# L2 Apply-Tenant Patch Contract

Status: draft
Scope class: staged closure blueprint
Purpose: define the intended L2 `apply-tenant` patch contract without
overstating current-tree runtime closure
Date: 2026-05-06

## 1. Purpose

NexusCRM already separates two install lanes:

1. `install-pack` for L1 product packs
2. `apply-tenant` for L2 tenant/admin overlays

This document freezes the intended contract direction for the second lane only.

It does not redefine:

- the L1 package contract
- the public package install model
- the current source-backed app-pack lifecycle

### 1.1 Salesforce baseline and declared delta

Salesforce API `v63.0` remains the compatibility oracle for runtime metadata
primitives and API shape.

This document defines an internal NexusCRM control-plane contract, not a claim
that `apply-tenant` mirrors Salesforce package/deploy tooling.

Declared delta:

- Salesforce compatibility applies to the runtime metadata model and API-facing
  primitives
- `apply-tenant` is a NexusCRM-specific L2 control-plane mechanism for
  tenant/admin overlay application
- that divergence is acceptable because it is outside the public Salesforce API
  contract and is explicitly scoped to internal/admin apply workflow

## 2. Boundary

### 2.1 L1

L1 remains the installable product pack lane.

Examples:

- standard CRM pack
- standard flows
- standard actions
- L1 default layouts, record types, validation rules, and permissions

L1 continues to use:

- `install-pack`
- `verify-pack`

### 2.2 L2

L2 is tenant/admin-authored overlay metadata layered on top of installed L1
packs.

Examples:

- tenant custom objects and fields
- tenant-specific layouts, record types, picklists, flows, validation rules,
  permissions, and sharing policy
- tenant business policy and tenant-specific automation

L2 is the only scope for this document.

### 2.3 Explicit separation rule

The two lanes must stay separate:

- L1 uses installable product pack semantics
- L2 uses `apply-tenant` overlay semantics

`apply-tenant` must not be described as the public package contract.

## 3. Current-Tree Status

Current repo evidence already supports the lane split:

- `backend/cmd/setup/main.go` exposes `apply-tenant` as a distinct setup
  command, separate from `install-pack`
- `backend/internal/controlplane/operations.go` defines `ApplyTenant` as a
  dedicated tenant-overlay path
- `backend/internal/controlplane/tenantoverlay/pack.go` describes tenantoverlay
  as generic machinery whose facts come from the operator-selected overlay
  directory

But current-tree runtime authority is not yet fully DB-only for all
behavior-affecting metadata surfaces.

Open evidence buckets:

1. installed source-backed runtime rebuild still reconstructs behavior-affecting
   metadata from source payloads:
   `RegisterInstalledRuntimeMetadata` states caches are rebuilt from the
   installed pack registry and pack payloads, and
   `RegisterRuntimeMetadataForPacks` appends runtime object-spec payloads into
   runtime registration and field-provenance inputs
2. object-spec-derived compatibility / identity fallback still exists on
   restart/runtime registration paths:
   `registerObjectSpecCompatibilityFallback(...)` and
   `compatibilityDeclarationsFromObjectSpecPayloads(...)` still synthesize
   identity and relationship declarations from object-spec payloads
3. multiple source-backed component paths still verify DB state against source
   payload projections:
   `source_backed_metadata_components.go` still contains multiple expected/live
   projection and source-backed verify paths

Therefore this document makes a narrow claim:

```text
this is the intended L2 contract direction
```

not:

```text
the current repo already runs all L2 runtime semantics from DB only
```

## 4. Intended Contract

### 4.1 Authority container

For L2 overlayable facts, the intended authority container after apply is:

```text
database state
```

not:

```text
overlay file contents as a continuing runtime authority
```

### 4.2 Apply model

`apply-tenant` should converge to a patch-apply model.

Minimum rules:

1. the patch may add, update, delete, or replace overlayable L2 facts
2. apply operates against current DB state
3. apply must be idempotent for the same patch input
4. apply must fail closed on ownership conflicts
5. unrelated L1 rows and unrelated L2 rows must remain untouched

### 4.3 Patch operations

Supported operation vocabulary:

- `add`
- `update`
- `delete`
- `replace`

Rules:

1. deletion intent is explicit
2. omission is not deletion
3. `replace` is limited to L2-owned overlayable facts
4. `replace` must not silently convert into L1 product takeover
5. `replace` does not create on miss; creation belongs to `add`
6. `update`, `delete`, and `replace` require a deterministically preexisting
   target at apply time

### 4.4 Replace target identity

`replace` requires deterministic targeting.

Minimum contract:

1. each replaceable surface must define the business key used to select the
   target row or row set
2. `replace` requires that the target row or row set already deterministically
   exists at apply time
3. each surface must define one-row vs many-row behavior explicitly
4. ambiguous target resolution must fail closed
5. missing target resolution must fail closed for `update`, `delete`, and
   `replace`
6. crossing from the intended L2 target into L1-owned rows or unrelated L2
   rows must fail closed
7. if a surface requires a preexisting baseline row, that rule must be
   explicit instead of inferred

Per-surface follow-up implementation must therefore specify:

- authoritative business key
- owner identity rule
- whether many-row replacement is legal
- which conflict cases abort apply

### 4.5 Verification semantics

Verification checks the DB result, not the patch file as a live authority.

Verify must answer:

1. did the patch land
2. is the resulting DB state valid
3. did the patch preserve unrelated L1 rows
4. did the patch preserve unrelated L2 rows outside its scope

Verify must also prove prerequisites for the next apply on a fresh tenant or
fresh org:

1. which surfaces require prior L1 materialization
2. which operations require a deterministically preexisting baseline row
3. whether the target identity required by `update`, `delete`, or `replace`
   currently exists
4. whether owner identity is already compatible with the intended L2 mutation

## 5. Target Runtime Rule

Target direction:

restart/runtime should converge to DB authority for L2 overlays.

Frozen target rule:

1. runtime rebuild must not re-interpret overlay files as authority after apply
2. runtime must not create a second source of truth for L2 state
3. restart must see the same DB outcome that apply produced

This is a target-state statement, not a claim that every current runtime path
already satisfies it.

## 6. Activation Rule

This document does not become the active runtime contract merely by existing.

It becomes the active L2 runtime/apply contract only after the current-tree
authority gaps from Section 3 are closed on the normal restart/runtime path.

Minimum closure buckets before activation:

1. restart/runtime rebuild no longer depends on source payloads for
   behavior-affecting L2 authority
2. payload-derived compatibility / identity fallback is closed or explicitly
   excluded from the L2 authority path
3. source-backed verify/read paths no longer act as a second authority for the
   L2 surfaces covered by this contract

Before those buckets are closed, this document should be read as intended
direction, not as proof of completed cutover.

## 7. Relation To Existing Docs

This document is consistent with:

- [nexuscrm-platform-architecture.md](./nexuscrm-platform-architecture.md)
  because it preserves the L1/L2 split
- [public-core-v1-definition.md](../open-source/product/public-core-v1-definition.md)
  because it does not promote private tenant payload into the public-core
  product boundary

It should be read as:

- an L2 target-state contract
- a transition design note

not as:

- a replacement for the L0/L1 ownership standard
- a claim that the repo has already completed DB-only L2 runtime closure
