# Component Resolution: Staged, Variant, Trunk

> A `remits-cli` skill reference. **Load this when** a change "is not working", you need to prove which version of a component actually ran, or several agents share one branch.
>
> The table of contents below carries **real line numbers** (`- L84  Some Heading`), resolved when
> this file is installed, so they are never stale. Read the head, pick your sections, and offset-read
> only those. The entry text is the heading verbatim, so it also greps.

## Table of Contents

- [Component Resolution: Staging Cache vs DB (which "version" actually runs)](#component-resolution-staging-cache-vs-db-which-version-actually-runs)
  - [The three source layers + the compile cache](#the-three-source-layers--the-compile-cache)
  - [Verification envelopes record the source layer](#verification-envelopes-record-the-source-layer)
  - [Staging cache key format](#staging-cache-key-format)
  - [How the platform picks staged vs DB (the compile signature)](#how-the-platform-picks-staged-vs-db-the-compile-signature)
  - [When staged overrides apply](#when-staged-overrides-apply)
  - [Diagnosing which version is in play](#diagnosing-which-version-is-in-play)
  - [Working alongside other agents: the staging WORKSPACE](#working-alongside-other-agents-the-staging-workspace)
  - [Keeping your remits-cli current](#keeping-your-remits-cli-current)
  - [A lane holds an OVERLAY; your workset is a different number](#a-lane-holds-an-overlay-your-workset-is-a-different-number)
  - [Stage / sync / clear with remits-cli](#stage--sync--clear-with-remits-cli)
  - [Stale after sync / commit (the in-memory compile cache)](#stale-after-sync--commit-the-in-memory-compile-cache)

## Component Resolution: Staging Cache vs DB (which "version" actually runs)

When the platform executes a component it resolves the source from one of two places, then compiles it
behind an in-memory cache. Understanding this is the difference between "my change isn't working" guesses
and a precise diagnosis.

### The three source layers + the compile cache

1. **CLI staging cache (Redis, 240-min TTL).** Branch + user + account scoped overrides written by
   `remits-cli components stage`. These shadow the layers below **only during CLI/test-mode execution**
   (see "When staged overrides apply" below).
2. **Committed branch variants (`ComponentVariant`, MySQL).** Durable, branch-scoped overlays of a
   component. Unlike staging these are **not** user-scoped, do **not** expire, and **do** apply to normal
   production traffic — for the accounts that subscribe to that branch. See
   `branch-variants.md`. Most accounts have none, in which case this layer is inert.
3. **Database trunk row (the committed live component).** What `mcp_component_view` reads, what an
   unsubscribed prod run uses, and what a trunk `commit` writes to.

Resolution order is **staged → variant → trunk**, and each layer *layers over* the one beneath it rather
than replacing it: a payload that only carries `source` inherits `path`, `objectType`, `inputSchema` etc.
from the layer below. A staged edit made on a variant branch therefore layers over **that variant**, not
over trunk.

Vocabulary matters because all three layers also carry branch-shaped fields. `branchName` is the git
branch / staging namespace. `componentBranch` or `variantBranch` is the committed `ComponentVariant`
overlay an account resolves. A **staging lane** is account + CLI user + git branch + workspace in Redis.
When writing a report, name the source layer and the branch kind explicitly; "the branch was verified"
does not tell the next agent what ran.

Plus the compile cache:

- **Compiled-closure cache (`BaseClosureDomain.CLOSURE_CACHE`).** An in-memory, **per-JVM-instance** Guava
  cache of the parsed closure, keyed by `(componentId, type, compileSignature)`. This is why a change that
  is correctly in the DB can still execute stale on a running instance — see "Stale after sync" below.

### Verification envelopes record the source layer

When a verification envelope is active, `components stage`, `components status`, `test run`, `token`,
`tool`, and `components sync` attach evidence packets with the branch/workspace/staging lane and the best
source-layer facts the command observed. A staged test packet is proof of the staged layer; a sync packet
is proof of a source transition; a token packet is proof of the browser token's resolution tuple. Those
are not interchangeable.

Use `remits-cli verify report` before summarizing the work. It will say when the evidence only covered
staged source, when committed variant/trunk proof is missing, when the manifest world does not match the
packet world, or when later packet facts made an earlier proof stale. Packets carry a lane content hash,
git head, workspace, data lane, and variant-world facts so a re-stage or branch move is visible.

### Staging cache key format

```
# default lane (no workspace)
account:<accountId>:cli:<cliUserId>:components:<branch>:<family>:id:<componentId>
account:<accountId>:cli:<cliUserId>:components:<branch>:<family>:name:<normalizedName>

# workspace lane
account:<accountId>:cli:<cliUserId>:components:<branch>:ws:<workspace>:<family>:id:<componentId>
```

The staging scope is therefore **account + cli user + branch + workspace**. `workspace` is optional and
absent by default; see "Working alongside other agents: the staging WORKSPACE" above.

`<family>` is the lowercased component family (`reader`, `action`, `test`, `embeddable`, ...). Both an
`id:` and a `name:` key are written per stage. The entry value carries: `kind` (the family), `type` (the
component's OWN type enum such as `ObjectType`/`RuleType`, or absent — **never** the family), `hash`,
`updatedAt`, the staged content field(s) (`source`/`prompt`/`html`/`javascript`/`schema`/
`inputSchema`/`previewData`), and `.meta.yml` metadata fields such as `description`, `summary`, `mermaid`,
`path`, Embeddable `injectionType`, `category`, and Schema flags (`enableTrigger`, `enableFullText`, `enableRAG`, `enableRevisions`,
`enableBigQuerySync`, `enableRules`, `anchor`, `auxiliary`).

### How the platform picks staged vs DB (the compile signature)

At compile time the platform computes a **signature** that tells you which layer won:

- Staged override present → `compileSignature = "cli:<hash>"` (the staged content hash).
- Committed branch variant → `compileSignature = "variant:<variantId>:<hash12>"`.
- Neither → `compileSignature = "version:<N>:<sourceHash12>"` (the DB row version plus a source hash
  prefix, so source changes cannot reuse a stale compile entry on the same instance).

The three namespaces are distinct on purpose: a component's staged, variant, and trunk closures coexist in
the compile cache without colliding.

That signature is logged. Querying for it is the single most reliable way to know what ran:

```bash
remits-cli tool --name mcp_system_logs --input '{"node":"remitsAdmin-east5","timeRange":"1h","filter":"Using Cached BCD"}' --data-mode prod
```

> **Pass the bare phrase — never hand-write a `textPayload:` filter.** In production the platform's
> logback encoder writes every `log.*` line to **`jsonPayload.message`**; only `println`/stdout lands in
> `textPayload`. A `textPayload:"..."` filter therefore matches **zero** rows for nearly every platform
> log line, and zero rows is indistinguishable from "it never happened" — which has already caused a real
> misdiagnosis. `mcp_system_logs` now widens a bare phrase (and any `textPayload:"..."` clause) to cover
> both shapes, and echoes the executed filter back as `filterApplied`. A filter that names `jsonPayload`
> explicitly is passed through untouched.

`Using Cached BCD [ID: 230, Type: Action, Signature: cli:08cc...]` → ran a **staged** override.
`...Signature: variant:14:9f2c1a...]` → ran a **committed branch variant**.
`...Signature: version:37:abc123def456]` → ran the **committed trunk** version.

### When staged overrides apply

Staged overrides resolve whenever the execution carries a **CLI-scoped TestMode** — i.e.
`TestMode.branchName` and `TestMode.cliUserId` are set. That includes:

- `remits-cli test run`
- `remits-cli tool`
- `remits-cli tools`
- tokenized runs minted with a branch-aware CLI token

For `remits-cli tool`, the CLI TestMode now stays active for the **entire tool execution**, not just the
top-level tool lookup. That means nested `reader()`, `action()`, `utility()`, `account.getTool()`, and
similar component resolution inside the tool also see the staged branch context.

`dataMode` is separate from staged resolution:

- `branchName` + `cliUserId` decide whether staged components can resolve.
- `dataMode:test|prod` decides which data surface the tool/test/token runs against.

So a `remits-cli tool --data-mode prod` call can intentionally execute **staged code against prod data**
for investigation or recall testing, while a normal live webhook / non-CLI runtime path with no CLI
TestMode still uses the committed DB source. Staging remains a dev/verification surface, not a deploy.

### Diagnosing which version is in play

- **See staging metadata for a component:** `mcp_component_view` (omit `fieldName`) returns `staging` /
  `stagedFields`, telling you whether a staged entry exists and which fields are staged.
- **Inspect the raw staged entry + TTL in Redis:** use `mcp_cache`.
  ```bash
  # find staged entries for one component
  remits-cli tool --name mcp_cache --input '{"action":"scan","pattern":"account:52:cli:*:components:*:reader:id:181","includeValuePreview":true}' --data-mode prod
  # dump one exact key
  remits-cli tool --name mcp_cache --input '{"action":"inspect","key":"account:52:cli:23:components:main:reader:id:181"}' --data-mode prod
  ```
  The preview shows `kind`/`type`/`hash`/`updatedAt` + a source snippet — confirm it's your content and
  that `type` is NOT the family (a family value in `type` is a tool bug that crashes hydration, e.g.
  `No enum constant ObjectType.reader`).
- **Confirm the DB version:** `mcp_component_view` reads the live DB source directly (no staging, no compile
  cache), so it is the source of truth for "what was committed."
- **Ask the CLI what is staged:** `remits-cli components status` lists this lane's staged entries,
  including staged fields, aliases, hashes, and TTLs. It also reports the other indexed lanes on the
  account so you can see parallel work before assuming you are alone. The default terminal output is
  concise; pass `--json` or `--verbose` when you need the full staged-entry payload.
- **Inspect another lane without impersonating it:** `remits-cli components lanes` lists every indexed
  lane on the account; `remits-cli components entries --lane-id <id>` reads the authoritative staged
  files for one lane. These are read-only review surfaces. They do not switch your workspace, clear
  anything, or change what your own test/token/tool runs resolve. Use
  `remits-cli components lanes --wait-change [--lane-id ID] [--timeout 600]` when you need to wait for a
  sibling lane to move; it watches the lane registry's `updatedAtMs`/content hash instead of making you
  poll in a loop.
- **Clear only your own lane:** `remits-cli components clear` removes entries when you intentionally want
  to fall back to DB source. `--all` is scoped to the command's account/user/branch/workspace lane, not
  every lane another agent may be using.

### Working alongside other agents: the staging WORKSPACE

Staging is scoped by `(account, cli user, branch, workspace)`. Account and user are fixed for a repo, so
**without a workspace the git branch is the only isolation axis** — and `components stage` uploads the
ENTIRE repo and REPLACES the lane rather than merging into it. Two agents on one branch therefore
overwrite each other, and a `components commit` clears the lane out from under the other one.

If more than one agent is working on the same branch, give each its own workspace:

```bash
git clone <repository-url> repo-agent-a                  # one CLONE per agent, same branch
cd repo-agent-a
git switch forked
git rev-list --left-right --count HEAD...origin/forked   # must print: 0  0
remits-cli workspace use --auto              # names the lane after this directory
remits-cli components stage                  # isolated: nobody else sees it, nobody overwrites it
remits-cli test run --test 42
remits-cli token --path /page/whatever
```

**A clone, not `git worktree add --force`.** Worktrees of one branch share its ref: a pull or commit in one
moves `HEAD` under the others, and a `components commit` from a stale one reverts work that already landed.
`components commit` refuses while its branch is checked out in another worktree (`--allow-shared-branch`
overrides, only once `git status` shows nothing but your own changes). A stale clone is still stale: before
the first edit in a new or reused clone, `git status --porcelain`, `git log origin/<branch>..<branch>`, and
`git log <branch>..origin/<branch>` should all be empty. For variant branches, run
`remits-cli components promotion --branch <branch>` too; a branch can be current with its own remote and
still stale relative to trunk.

A workspace narrows STAGING and nothing else. A commit still targets the same branch and the same owner
account, and the run still resolves whatever committed variant branch the account subscribes to — so it
does **not** have the side effects of inventing a throwaway git branch per agent (which would make a
commit write `ComponentVariant` overlays for a branch nobody subscribes to).

Workspace is not the repo-local filesystem identity. The CLI also has a **local actor** namespace
(`REMITS_AGENT_ID` or `--local-agent`) for `.remits-cli/actors/<local-agent>/` session logs, tool
responses, verification mirrors, and diagnostics. Two agents that share one checkout should set distinct
local actors even if they intentionally use the same or different staging workspaces. Run
`remits-cli doctor local-state` to see the active actor, state directory, legacy flat state, and other
actor directories.

A local **workstream id** groups one logical proof campaign across those isolated actor directories without
flattening them. It defaults to the current workspace. Override it with `--workstream <id>` or
`REMITS_WORKSTREAM_ID` when several actors are collecting evidence for one task, then inspect the local
index with `remits-cli workstream status`.

- `.remits-cli/workspace` is per-checkout and gitignored, so each clone keeps its own lane.
- Precedence: `--workspace NAME` > `REMITS_WORKSPACE` > `.remits-cli/workspace` > shared default lane.
- `--no-workspace` targets the shared lane for one command without clearing the file.
- Every stage / test run / token / clear prints its `Staging lane:` — if a change seems to have had no
  effect, check that line FIRST. A mismatched lane resolves committed source, which looks identical to
  "the stage did not work".
- `remits-cli components status`, `components stage`, `test run`, and `token` include the current
  account's lane context in their responses. A shared current lane is printed loudly; sibling lanes are
  listed when they matter.
- `remits-cli components lanes` is the review view across users/branches/workspaces, and
  `remits-cli components entries --lane-id <id>` is the drill-down into actual staged files. When you are
  coordinating with another agent, prefer `components lanes --wait-change --lane-id <id>` to repeated
  status checks.
- `remits-cli components clear --all` is scoped to YOUR lane and never touches another agent's.
- In lane rows, `currentLane` (also `mine`) marks THIS command's lane; `ownedByCaller` marks every lane
  staged by your CLI user — your other clones' agents included.
- If your current lane is empty but the same workspace has staged entries on another branch, the CLI
  prints a warning. That usually means the checkout switched branches after staging; re-stage on this
  branch or switch back.
- **A landing clears only the lander's lane.** Every stage records the commit it came from
  (`stageBaseSha`), and `components status` compares that with the last commit the platform synced for the
  branch (`branchContext.lastSyncedSha`) using your local git:
  - `LANDED SINCE YOUR BASE` — the platform synced a commit this checkout does not contain. Somebody landed
    after you pulled: `git fetch origin && git pull --ff-only`, then re-stage.
  - `STALE OVERLAY` — entries staged from a commit older than the last sync. They shadow rows that landed
    after they were staged; re-stage with `--workset` or clear them.
  - `--json` carries the same answer as `freshness` (`landedSinceHead`, `entriesBehindLastSync`, `stale`).
    `null` means unknown — a lane staged by an older CLI, or a branch whose last sync is not recorded —
    never "fresh".

### Keeping your remits-cli current

The diagnostics in these references only exist in the CLI that prints them. An older install does not print a
worse version of them — it prints nothing, with no error, so nothing tells you what you are not being shown.
`components stage` and `test run` warn when yours is behind:

```text
[remits-cli 0.1.120 -> 0.1.136] ... npm install -g @remits/remits-cli@latest
```

Upgrade when you see it. Auto-update handles it for you on most commands, including `--json` ones.

### A lane holds an OVERLAY; your workset is a different number

This is the distinction that decides whether a lane is legible to anyone but you.

- The **overlay** is every staged entry the lane currently holds. It is what a CLI-scoped run resolves,
  and it is the number the console shows as "staged".
- The **workset** is what git reports this working tree changed. It is the work in flight.

A plain `components stage` is a FULL SNAPSHOT: it uploads the whole repository manifest and reconciles
the lane to it, so on a 115-component repo the overlay is 115 whether you edited five components or all
of them. That is safe — it is a complete, known state — but it is a poor signal. Everyone reading the
console sees a lane that looks like 115 edits in flight, and all 115 entries shadow committed source for
every run in that lane until they expire.

`components stage --workset` uploads only the changed components and reconciles the lane to exactly
them, so the overlay IS the workset. That is the mode to iterate in.

`components stage --changed-only` uploads the same narrow set but MERGES: it deliberately leaves every
other staged entry alone. So it can never shrink a lane inherited from an earlier full snapshot — the
overlay stays at 115 while you work on seven. The command warns when entries are retained that way.

`components commit` uses that same merge shape for its internal compile-validation stage before git
writes. Treat it as "compile the changed source in the current overlay", not "make the overlay equal my
workset". If lane cleanliness is part of the proof, run `components stage --workset` and verify before
the commit/sync path.

Every count is `unknown` rather than `0` when it cannot be established. "git could not answer" and "git
says nothing changed" are different facts and only one of them is a number.

**Deletion is not expressible here.** There is no staged removal: clearing a staged entry falls back to
the committed row, so the component still resolves. `stage --workset` reports a deleted component file as
NOT REPRESENTABLE rather than quietly omitting it. On a non-trunk variant branch, prove a deletion
through the durable variant plan — `remits-cli components sync --dry-run --summary --fail-on-errors` —
and read the removed/tombstone bucket. On trunk there is no dry-run plan; a deletion is only proven by
the full pre-sync safety check before a mutating reconcile.

**An empty workset never clears the lane.** `--workset` on a clean tree stages nothing and leaves the
lane as it is; reconciling to an empty manifest would delete the overlay the next run depends on.
Clearing stays explicit (`components clear --all`), or `--empty-workset clear` if that really is what you
meant.

### Stage / sync / clear with remits-cli

- `remits-cli components stage --workset` stages exactly what git says this working tree changed and
  reconciles the lane to it. **The normal iteration mode.**
- `remits-cli components stage` stages the whole repository manifest (a full snapshot). Use it for a
  deliberate complete snapshot, when a `.meta.yml` key you deleted must be reconciled against the whole
  repo, or as a "what is stale in here?" reset — then clear when you are done.
- `remits-cli components stage --changed-only` stages just the changed components and does NOT reconcile,
  so entries it did not mention are left alone rather than deleted. Kept as-is for compatibility;
  `--workset` is the same narrow upload with the lane reconciled.
- `remits-cli components status` shows which branch/variant world the checkout resolves, whether the lane
  is shared, the overlay/workset/retained split with the last stage's mode, plus staged entries, staged
  fields, aliases, hashes, and TTLs. Use `--json` or `--verbose` for the full staged-entry payload.
- **A full `stage` makes the `.meta.yml` AUTHORITATIVE.** Staging layers a payload over what is already
  staged, which is what lets `mcp_component_edit` write a single field without blanking the others. But a
  `remits-cli components stage` sends the whole sidecar, so a key you DELETE from a sidecar is removed from
  the staged entry rather than lingering — "restore the file and re-stage" restores the staged state, which
  is the only mental model that is safe to have. Content fields still layer (they come from separate files),
  so a partial stage is unaffected.
- `remits-cli components clear` drops staged entries when you intentionally want to fall back to committed DB
  source. An empty staging scope is clean state, not a failure.
- `remits-cli components sync` syncs the DB from the pushed git remote and then clears staged entries for the
  synced components, so a clean promotion leaves a clean staging cache. Because sync reconciles the remote repo
  into the live component database, it must pass `component-integrity.md` first. Never use sync only to
  clear staging, to recover from a mismatched ID, or to retry after an unexpected create/delete/rename response.

### Stale after sync / commit (the in-memory compile cache)

After a `git sync` or a `commit` updates the DB source, a **running instance can keep executing the
previously-compiled closure** until the version-keyed signature changes and that instance's
`CLOSURE_CACHE` misses (or the instance recycles). Symptoms: `mcp_component_view` shows the new source, but
behaviour (or a freshly-staged entry produced by an edited *tool*) still reflects the old code. This is the
standard Grails no-hot-reload caveat — it is environmental, not a code defect. Verify the live entry/source
with `mcp_cache` / `mcp_component_view`, and if a platform/tool source change must take effect immediately,
the platform owner recycles the instance.
