# CLAUDE.md — Project Instructions for AI Agents

## Project Overview

NexusCRM: a Salesforce-compatible CRM platform. Go backend + React frontend + TiDB. The frontend was built against the real Salesforce API v63.0, making it the living API specification.

---

## Metadata-Driven Architecture — Mandatory Rules

**This is the single most important architectural rule of this project.** Every PR must satisfy it. A feature that "works" but violates this section is not acceptable and must be re-designed, not patched.

### Core Definition (the spine — read this first)

> **This section is the formal definition of metadata-driven architecture for NexusCRM.** The four commitments are necessary conditions; the three decision tests are decidability procedures (each test checks specific commitments); R1–R11 below are consequences; *Coding Red Lines* further down is the enforcement layer with grep / Q&A checklists.

> **The platform is four layers. L0 platform code is an interpreter. L1 standard metadata is the platform's factory defaults that ship in the binary. L2 org metadata is admin-editable per-org configuration. L3 record data is user-produced rows. Each layer has its own author, lifecycle, and change cost; every fact has a unique representation in its proper layer, read through a single path. (Full layer definitions in the *Four-layer separation model* below.)**

#### Four Commitments — violating any one means the system is not metadata-driven

1. **Author decides layer.** Engineer-authored facts live in **L0** (code) or **L1** (standard metadata that ships in the binary, e.g. `standard_objects_spec.json`, standard permission keys). Admin-authored facts live in **L2** (`_System_*` rows). User-authored facts live in **L3** (SObject rows). Misplacing a fact by one layer multiplies its change cost by an order of magnitude — an admin's 30-second task becomes a week-long engineering cycle.
2. **The L0 interpreter is blind to L2 instances.** L0 engines know schema shape only (`ObjectMetadata.Fields`, `Field.Type`, `IsCustom`), never specific instances (SObject names, `__c` field names, ProfileIds, picklist values, tenant table names, RecordType DeveloperNames). Object-specific behavior is expressed as L2 metadata rows (Trigger / Flow / ValidationRule / ApprovalProcess) and dispatched by a generic L0 engine. L1 may carry standard names (`Account`, `OwnerId`, `ConvertLeads`) that are part of the Salesforce contract; it must never carry tenant artifacts (`PingCAP`, `TiDB`, `AliCloud_Channel__c`, tenant approval-process names) — those are P0 leaks.
3. **One fact, one SSoT, one read path, one owner.** For any fact SF API v63.0 already models, read SF's representation; never invent a substitute. For any `(user, object, record)` decision, every layer — object CRUD, record access, query RLS, seeder — goes through the same metadata helper; agreement across paths today does not save you, multiple paths *is* the defect. Facts derived from SObject shape live in the metadata service; consuming features call helpers and never own a shape registry of their own.
4. **Exhaust reuse before inventing.** Strict order: SF primitive → existing platform primitive → extend an existing primitive into a generic metadata-driven capability → new primitive. Skipping a tier without exhausting it is a defect. Any new primitive must declare its delta from SF, the justification, the impact on the next tenant, and how the claimed properties will be verified. *Corollary — diagnose substrate vs feature.* When a feature is hard to express as metadata, the engine gap is the work; imperatively re-implementing the feature in Go is misdiagnosed Phase-1 work and produces a bespoke handler the next refactor will have to delete.

#### Three Decision Tests — decidability procedures, run on every code change and every design

- **Greenfield Test — checks C1 + C2.** Pretend this diff is deployed to a brand-new org with no `__c` fields, no `system_admin` profile, no tenant tables. Walk every changed code path. Anything that crashes or behaves wrong → violates Commitment 1 or 2.
- **Admin Self-Service Test — checks C4.** Pretend an admin wants to change the rule this PR introduces (add a region, change a default RecordType, open a field for write). Can they do it through metadata or UI alone? If they need an engineering PR → violates Commitment 4.
- **Two-Path Consistency Test — checks C3.** Count the read paths for this fact. Do they all go through the same helper? If a metadata row changes, do all paths see it instantly? If no → violates Commitment 3.

**Concrete grep at PR time** (operationalizes Greenfield + Admin Self-Service): `rg '__c|ProfileId\s*[=!]==|"NA"|"APAC"|"Open Opportunity"' <changed-files>` — every non-test hit needs written justification or removal. Full command set in *Coding Red Lines → Grep Test* below.

#### What it is NOT (boundary clarifications)

- **Not "externalized config."** Moving hardcoded values into a YAML while the author is still an engineer and the lifecycle is still deploy-and-restart is not metadata-driven — it is relocation.
- **Not "everything goes into a database."** The interpreter itself must live in code; standard metadata must ship in the binary. The layer must match the fact, no more, no less.
- **Not "no `if` statements."** Branching on schema shape (`Field.Type == Picklist`, `ObjectMetadata.IsCustom`) is correct. Branching on a specific instance (`objectName == "Lead"`, `Region == "APAC"`, `ProfileId === 'system_admin'`) is the violation.
- **Not "blindly mirror Salesforce."** For facts SF has modeled, reuse SF's representation (Commitment 3). For facts SF has not modeled, extension is allowed (Commitment 4) — but the delta is declared, never silently introduced.

#### The mantra (compressed canonical form)

> **Author decides layer. Interpreter is blind to instances. One fact, one SSoT, one read path. Exhaust reuse before inventing.**

Before any code change or any design, check the four lines. If they hold, the work is metadata-driven; if any breaks, it is not — regardless of what it is called. The *Coding Red Lines* tables define R1–R11 and operationalize them as grep / Q&A enforcement. Return to the spine when in doubt.

### Concrete scenario — how platform, org metadata, and user data relate

Before the rules, walk through one end-to-end example. This makes the layer split feel obvious.

**Scenario.** PingCAP's admin needs: "On Account, track Country; require Country when `Region__c='APAC'`."

**Where does each piece live?**

**Platform code (ships in the binary, identical for every org):**
- The generic CRUD engine that handles `POST /sobjects/<anything>`.
- The generic describe engine that tells the UI what fields an object has.
- The generic validator that walks `_System_ValidationRule` rows and evaluates each.
- **No literal "Account", "Region", "Country", "APAC", or "PingCAP" appears in this code.** Same engine handles Lead, Account, Custom_Object__c — polymorphic over SObject shape.

**Standard metadata (ships in the binary, identical for every org):**
- `Account` declared as a standard SObject in `standard_objects_spec.json`.
- Standard fields (`Name`, `OwnerId`, `CreatedDate`, `Industry`) with standard picklist values.
- Standard permission keys (`ConvertLeads`, `ModifyAllData`, `ViewSetup`).
- **Zero mention of `Country__c`, zero mention of `PingCAP`.** A greenfield CRM customer gets the same standard metadata and nothing of PingCAP's.

**Org metadata (per-org, stored as rows in `_System_*` tables, admin-editable):**
- PingCAP's admin inserts a row in `_System_ObjectField`: `{ObjectApiName: 'Account', ApiName: 'Country__c', Type: 'string', ...}`.
- Inserts rows in `_System_PicklistValue` for a `Region__c` picklist: `["NA","EMEA","APAC","Japan"]`.
- Inserts a row in `_System_ValidationRule`: `{Object: 'Account', Formula: "ISPICKVAL(Region__c, 'APAC') && ISBLANK(Country__c)", Error: 'Country is required'}`.
- Inserts rows in `_System_FieldPermissions` granting the `Sales` PermissionSet write access to `Country__c`.
- **Every line above is an admin action via UI or API. Zero code changes. Zero deploys. Another org (e.g. Stripe) has a completely different set of rows — no `Country__c`, different rules, different permissions.**

**Record data (business rows users create):**
- PingCAP's `Account` table gets `{Id: 'A001', Name: 'TiDB Bank', Region__c: 'APAC', Country__c: 'Japan'}`.
- Stripe's `Account` table has its own rows referencing Stripe's fields.

**Runtime trace of a user POST:**
1. User sends `POST /sobjects/Account {Name: 'Acme', Region__c: 'APAC'}`.
2. Platform CRUD engine loads **this org's** `_System_ObjectField` rows for Account → sees `Country__c` exists.
3. Engine loads this org's `_System_ValidationRule` rows → sees the "APAC requires Country" rule.
4. Engine evaluates the rule's formula → `Country__c` is blank → returns 400 with the admin-authored error message.
5. **The engine executed PingCAP-specific business logic without containing a single PingCAP string.**

**Onboard a new tenant "Stripe" tomorrow:**
- Same binary. No deploy.
- Stripe's admin sets up Stripe's org metadata (different fields, different rules).
- Stripe users POST records. Same engine. Same performance.
- **Zero engineering work.**

**When the layers collapse:** suppose the platform code contained `if objectName == "Account" && body.Region == "APAC" && body.Country == "" { return error }`. Stripe's admin now wants "Region='EU' requires VAT_ID" → engineering ticket → PR review → deploy → every org restarts. **A 30-second admin task becomes a week-long engineering cycle.** This is the exact failure mode metadata-driven architecture exists to prevent.

### Salesforce's three engine primitives

The Core Definition becomes concrete when you see how Salesforce decomposes runtime behavior into three primitives. Every rule below follows from these:

1. **`ObjectDescribe`** answers *"what does this object look like?"* — fields, types, picklist values, record types, layouts, defaults, formulas, relationships. **All rows**, not code. The UI reads it; the CRUD engine reads it; validators read it.
2. **`ObjectPermissions` / `FieldPermissions` / `SharingRule` / `RecordShare`** answer *"who can do what to which record?"* — all rows, evaluated by the permission engine. Never "if ProfileId == X" in code.
3. **`ValidationRule` / `Flow` / `Trigger` / `ApprovalProcess` / `WorkflowRule`** answer *"what happens when a record is saved / submitted / approved?"* — all rows, evaluated at runtime by the respective engine.

### Four-layer separation model (how the above lives in this repo)

```
┌─────────────────────────────────────────────────────┐
│ L0  Platform Code (Go / TS)                          │  ship-once, all orgs
│     CRUD engine, permission engine, flow engine,     │
│     approval engine, layout renderer.                │
│     Knows schema shape only. Knows ZERO tenant names.│
├─────────────────────────────────────────────────────┤
│ L1  Standard Metadata (ships in the binary)          │  ship-once, all orgs
│     Standard SObjects + standard fields.             │
│     Standard picklists (e.g. Account Industry).      │
│     Standard Permissions (ConvertLeads, ViewSetup…). │
├─────────────────────────────────────────────────────┤
│ L2  Org Metadata (per-org, admin-configurable)       │  each org has its own
│     `__c` fields, picklist values, RecordTypes,      │
│     LeadConvertSettings, ApprovalProcess, Layouts,   │
│     FieldPermissions, PermissionSet, SharingRule,    │
│     ValidationRule, Flow, Formula.                   │
├─────────────────────────────────────────────────────┤
│ L3  Record Data                                      │  user business rows
└─────────────────────────────────────────────────────┘
```

- **L0 code may never reference an L2 identifier** — no `__c` names, no tenant picklist labels, no tenant RecordType DeveloperNames, no tenant approval-process names, no hardcoded ProfileIds.
- **L1 seeds may never contain tenant artifacts** — `controlplane/packs/**`, the rest of `controlplane/**`, and `bootstrap/**` ship to all orgs. A `"PingCAP_*"` identifier or a `TiDB` product name in these files is a P0 leak. Tenant-specific data lives under `controlplane/tenants/<tenant-id>/` and is opt-in per deployment via `NEXUSCRM_APPLY_TENANT_OVERLAY=1` + `cmd/setup apply-tenant`; greenfield orgs never apply it.
- **L2 is where per-org customization lives** — managed by the control plane per org, not hardcoded in the binary.

### What violation looks like: the #1574 contrast table

PR #1574 (reverted in `a2ef61dc`) is the canonical counter-example. Three concrete business needs, each solved **the Salesforce way** vs **the anti-pattern way**. Same outcome for today's users — but only the left column survives a new org, a renamed profile, an admin-driven policy change.

| Business need | Metadata-driven (correct) | #1574 anti-pattern (rejected) |
|---|---|---|
| "After Lead is converted, only Status + Approval fields are writable" | `FieldPermissions` rows per field + `ValidationRule` with `ISCHANGED(nonWhitelistField) && IsConverted`. Admin changes the whitelist by editing rows. | Go map `convertedLeadWritableFields = {"Status","Approval_Status__c","Approval_Reason__c"}` in `persistence_service.go`. Whitelist change requires a code PR. Other orgs don't have `Approval_Status__c`. |
| "Country → Region → which approval process" | `Region__c` picklist + Formula (or Flow) mapping Country to Region; `ApprovalProcess.entryCriteria` uses `Region__c` to select the process. Admin adds a country/region by editing picklist rows. | Go SQL `FROM country_dictionary__c` (tenant table) + map literal `{"NA":"Lead_Convert_Approval_AN_And_EMEA","APAC":"…","Japan":"…"}` baked into seeds. Adding a region needs code. |
| "System admins can skip Opportunity creation on convert" | New permission `SkipOpportunityOnConvert`, granted via `PermissionSet`; frontend calls `useSystemPermission('SkipOpportunityOnConvert')`. Any profile can be granted it. | `runtime?.user?.ProfileId === 'system_admin'` string match in the React modal. Rename the profile, ship a new org, or want to grant this to Sales Ops → code change. |

The common shape of every violation in that PR: **platform code learned a fact about one specific org**. The fix is always the same — push the fact down into metadata rows and let the generic engine read it.

### Rules R1–R11 — consequences of the four commitments

L0 platform code = backend Go under `backend/internal/**` (except `bootstrap/` and `controlplane/packs/`) and frontend TS under `frontend/src/**`. R1–R11 are the concrete enforcement patterns derived from the four commitments. Their canonical definitions live in the *Coding Red Lines* tables below.

### What belongs where (quick reference)

| Concern | Correct home | Wrong home |
|---|---|---|
| Custom field (`Foo__c`) | `_System_ObjectField` rows (L2) | `standard_objects_spec.json`, Go const, TS literal |
| Picklist values | `_System_PicklistValue` rows | switch / if-chain in code |
| Default converted status / default RecordType | `LeadConvertSettings` (L2 per-org config) | Go const, seed string literal |
| "Who can do X" | `PermissionSet` + `ObjectPermissions` + `FieldPermissions` | ProfileId literal match |
| "Which approval process applies" | `ApprovalProcess.entryCriteria` | hardcoded region→process map |
| "Which fields are writable after convert" | `FieldPermissions` + `ValidationRule` | Go map allowlist |
| SObject-specific side-effect on save | `Flow` / `Workflow` / `Trigger` metadata | `if objectName == "Lead"` branch in persistence |
| Tenant country→region mapping | Org-level seed or admin UI | Platform-binary `_System_CountryRegionMapping` seeds referencing `country_dictionary__c` |

### Tenant quarantine: `backend/internal/controlplane/tenants/<tenant-id>/`

Tenant-specific metadata (PingCAP `__c` fields, tenant RecordType names, tenant picklist values, tenant autonumber formats, tenant action overlays, tenant page-layout overlays) lives in **exactly one place**: `backend/internal/controlplane/tenants/<tenant-id>/`. This directory is applied only by `cmd/setup apply-tenant`, which `infra/entrypoint.sh` invokes **only** when the deployment declares `NEXUSCRM_APPLY_TENANT_OVERLAY=1`. Greenfield orgs leave the env unset and never see tenant rows in `_System_*`.

### Forbidden outside the tenant quarantine

Anywhere in the platform source tree **other than** `controlplane/tenants/<tenant-id>/` — including `controlplane/packs/**`, the rest of `controlplane/**`, `bootstrap/**`, `features/**`, `interfaces/**`, and frontend `frontend/src/**` — the following are P0 violations regardless of convenience:

- Any identifier containing `PingCAP`, `TiDB`, `AliCloud`, `HubSpot`, `PoC`, `IQM`, `Heavy_User`, or any other single-tenant product / channel / process name.
- Any `__c` field whose semantics are tenant-business-specific (the test: would a greenfield CRM customer have this field by default? If no, it's L2 and belongs in the quarantine).
- Any RecordType DeveloperName that names a specific product or legal entity (e.g. `PingCAP Master Agreement`, `TiDB Cloud PoC Agreement`).
- Any picklist value that is a specific tenant's legal entity list (e.g. `PingCAP Kabushiki-Kaisha`).
- Any approval-process DeveloperName (ship zero approval processes in the platform binary).
- Any country / region / currency seed list that matches one tenant's GTM taxonomy rather than an ISO standard.

Inside the quarantine directory these identifiers are expected and allowed. The CI gate `.github/workflows/platform-binary-cleanliness.yml` enforces the boundary.

### Permitted "hardcoded" strings (these are NOT violations)

- **Standard Permission keys** used as lookup identifiers: `"ModifyAllData"`, `"ViewSetup"`, `"ConvertLeads"`. These ARE the metadata keys.
- **Standard SObject audit fields**: `Id`, `Name`, `CreatedDate`, `CreatedById`, `LastModifiedDate`, `LastModifiedById`, `OwnerId`, `IsDeleted`, `RecordTypeId`. These are part of the SObject contract.
- **`_System_*` infrastructure table names** used by platform engines (`_System_User`, `_System_ObjectPermissions`, `_System_FieldPermissions`, etc.) — these are L1, not L2.
- **Security-sensitive field name allowlists** (passwords, tokens, secrets) that gate BEFORE FLS — these are hardening, not permission logic.
- **Sentinel values defined and consumed within the same module** (e.g. `"__TODAY__"` for date tokens) — acceptable if no tenant meaning leaks.

### Legacy remediation belongs in data specs, not platform code

When a bug fix discovers historical bad data, obsolete physical storage, or a
repository-specific migration mistake, do not preserve that history as a
long-lived branch in the runtime, generic engines, or control plane. The
platform should keep the generic metadata-driven model; the remediation should
live in `data-specs/` as an explicit one-time upgrade runbook.

The correct split is:

- **Platform code** implements the durable model: metadata catalog, SSoT
  storage, generic readers/writers, and boundary projections.
- **Data specs** repair legacy rows/columns/tables created by previous
  versions or external loaders, then get out of the runtime path.

Example: if a refactor makes `_System_PermissionSetPermission` the SSoT and
`PermissionSet.Permissions*` becomes a Salesforce wire-shape projection, do not
add a permanent runtime/control-plane branch that knows how to clean up old
physical `Permissions*` columns. Keep the platform on junction SSoT +
metadata-driven projection, and use a data spec to backfill/drop the legacy
columns on upgraded clusters.

---

## Refactor Discipline

If your PR matches **any** of these specific triggers, read `docs/lessons/2026-04-23-permission-ssot-cutover.md` before shipping:

- **SSoT cutover** — a read path is being flipped to a different storage
- **Staged refactor** — this PR delivers part of a multi-PR refactor with deferred tails
- **Destructive migration** — the diff includes `DROP COLUMN`, `DELETE FROM`, `TRUNCATE`, or schema removal combined with a backfill step
- **Shared-state bug fix** — a user-visible bug in permissions, metadata, records, or any subsystem whose state is read by multiple paths

The triggers are deliberately narrow. A routine feature PR or a localized bug fix is out of scope — this document is not a generic "large PR" checklist.

**Two acceptable shapes — never a third.** When a refactor cannot ship the right shape this iteration, the PR is one of:

- **Narrow-scope-correct.** Remove the wrong-shape part from this PR; open a tracking issue naming what is deferred and the prerequisite (substrate landed, design doc accepted, primitive built) that must be true before re-attempting. The deferred work does not return until the prerequisite is satisfied.
- **Atomic-cutover.** Land *all* of: new mechanism + every read path switched + old mechanism deleted + data-spec triplet (backfill, audit, destructive) — within one PR. The audit spec is separate from the destructive spec and must require zero rows on every cluster before destruction is permitted.

"Land now, fix the shape later" is not a third option. Phrases like *follow-up PR will…*, *transitional state*, *for now*, *we'll migrate the old path next* in a PR description are all the same option, and that option is rejected. Scope is the variable; correctness of shape is not.

---

## Correctness Model — Four Axioms

### Axiom 1: External Oracle — Salesforce API v63.0

SF API v63.0 is the external oracle. The frontend was built against a real, production SF instance.

- **Frontend = specification** (verified contracts)
- **Backend = implementation** — a missing/wrong backend behavior is unfinished work, not a frontend bug
- **Never change frontend API calls to accommodate backend**
- **Frontend changes require SF API v63.0 verification first**

### Axiom 1.1: Product Form Parity

Product design should align with Salesforce **at the boundaries that bind admins, integrators, and downstream tenants** — API contracts, data model, authoring model, install topology, operational visibility. Implementation must stay metadata-driven. Engine may differ, but product-boundary drift must be explicit.

**Apex/script positioning.** Salesforce uses Apex to carry installable business code. NexusCRM does not clone the Apex language, sandbox, or managed-package executable-code model. NexusCRM "script" has a current implementation subset (Flow metadata, Formula/Expression evaluation, and Go-backed Invocable Actions) and a broader design target: Apex product-role capability equivalence through AI-agent-friendly metadata/script orchestration plus extensible Go-backed built-in functions/actions — what differs from Apex is the authoring model and runtime mechanism, not the intended CRM product-role capability set. In reviews, classify each Apex surface using the Status Vocabulary in `docs/design/script-layer-apex-positioning.md`. Do not treat "not implemented today" as "not in the design"; do not treat "architecturally implementable" as "already implemented." For any design or review involving Apex, script, Flow/Formula extensibility, Invocable Actions, or package-owned executable logic, read `docs/design/script-layer-apex-positioning.md` in full before judging parity.

**Strict-parity surfaces (high cost of divergence — must match SF or declare delta with strong justification):**
- Public REST/SOAP/Tooling/Bulk API shape, status codes, query semantics
- Object/field/permission data model (`SObject`, `__c`, `RecordType`, `FieldPermissions`, `SharingModel`, …)
- Authoring model (Flow / ValidationRule / ApprovalProcess / Layout structure that admins touch)
- Install/upgrade contract and tenant overlay topology

**Soft-parity surfaces (Salesforce is the starting baseline, not the target — declared deltas are normal and expected):**
- Frontend visual presentation, layout, navigation grouping (e.g. tabs vs split nodes), interaction patterns, microcopy, design-system primitives
- Setup chrome (page header, icon style, action placement)
- List view ergonomics (sort defaults, density, batch-action affordance) above what SF documents as required

The first set is where tenant-portability and integrator trust live, so deviation is expensive and rarely justified. The second set is where NexusCRM's product team is allowed (and expected) to make NexusCRM-specific decisions — slavishly copying SF's UI pixel-by-pixel is not a goal. **Implementation metadata-driven-ness is non-negotiable in both cases.**

Design rules:

1. Start with the Salesforce baseline: product form, configuration path, and extension model. Use it as a **reference**, not a target to copy mechanically.
2. Align hard on external contract, authoring model, install topology, and operational visibility (the strict-parity surfaces above).
3. Keep the implementation metadata-driven. If it can live in metadata / data, it must not live in platform code.
4. Reuse existing primitives first. If a primitive must be extended, do it as a generic metadata-driven platform capability.
5. Do not invent a new primitive until the Salesforce paths, current platform paths, and reusable extension paths have been exhausted.
6. **Declared-delta protocol** — if the design diverges from Salesforce on either surface set, write down: (a) the delta, (b) which surface set it falls in (strict / soft), (c) the justification (UX research, NexusCRM design choice, technical constraint, …), (d) next-tenant impact, (e) how it will be verified. Soft-surface deltas are routine and require a one-paragraph note; strict-surface deltas need stronger justification proportional to the divergence.

### Axiom 2: Formal Verifiers + Runtime Oracles

Compiler + tests + static analysis are incorruptible judges:

```bash
go build ./...    # type correctness
go test ./...     # behavioral correctness on written cases
go vet ./...      # static defect detection
```

They prove nothing about rendered behavior or real-system races. Two runtime oracles are mandatory.

**Frontend runtime oracle — captured browser evidence.** What's mandatory is the **evidence**, not a specific tool: a transcript or screenshot from a real browser driving the rendered app, captured against the diff under review. Tool catalog and discovery at `.agent/skills/browser-agent/SKILL.md`.

You **MUST** capture browser-rendered evidence whenever:
- A task or PR touches `frontend/src/**` (default values, sort order, initial state especially — static review can't catch these)
- Reviewing test coverage for a frontend change — "covered" without a captured browser transcript or screenshot is **unverified**
- Reproducing a user-reported UI bug — reproduce, screenshot, cite path

**Backend CRUD runtime oracle — `tests/e2e-api/cases/`.** Full policy at `.agent/skills/e2e-test-policy/SKILL.md`.

You **MUST** add an e2e-api case whenever a PR observably changes behavior in:
- `backend/internal/features/records/**` (PersistenceService, gates, validators)
- `backend/internal/interfaces/rest/sobject_records.go` (SObject CRUD HTTP entry)
- `backend/internal/features/automation/**` (flow/trigger/autonumber on save)
- `backend/internal/controlplane/packs/standard_actions.json` (action visibility or server-side workflow)

Exempt (implicit, no PR-body line needed): pure refactors, renames, dep bumps, logging, docs, test-only diffs.

A claim of "covered" for a backend CRUD change without a `tests/e2e-api/cases/**` diff is **unverified** by default.

### Axiom 3: Evidence Gate — Falsify, Don't Confirm

Every claim needs evidence. Verdicts use three values:

- **CONFIRMED** / **PARTIALLY TRUE** / **FALSE**
- Unverifiable claims are labeled **Unverified** — admitting ignorance beats pretending certainty
- Nothing enters the Code Map without Challenger verification

Decision matrix:

| Worker result | Challenger verdict | Action |
|---|---|---|
| Improved | Confirmed | Keep |
| Improved | Found issues | Investigate and fix |
| No change | Confirmed correct | Keep, document why |
| Regressed | — | Revert with `git revert` or escalate |

### Design Review Gate

For any design doc / ADR / PRD, the reviewer must be able to answer all six:

1. How does Salesforce solve this, and what candidate paths exist?
2. What repo evidence was checked (`file:line`)?
3. What deltas from Salesforce are declared?
4. Why is each delta justified?
5. What changes for the next tenant?
6. How are the claimed properties verified?

If any answer is missing, send the doc back. Missing verification means the claim is **Unverified**, not true.

### Axiom 4: Minimum Change — Blast Radius Control

- Make the **smallest** change that fixes the issue
- One concern per commit, no drive-by refactors
- Stop after 5 improvements / 10 attempts / 3 consecutive no-improvement rounds
- Every changed line traces to the user's request

Causality stays deterministic; revert cost stays minimal; Challenger's verification burden stays bounded.

### Merge Gate

Every fix must pass `/review` before merge. Details live in `.claude/commands/review.md`.

---

## 🔴 Coding Red Lines — Mandatory Review Standard

**ANY violation = instant rejection (BLOCK).**

Before any code change, read `.agent/skills/coding-red-lines/SKILL.md`.

This section is the canonical definition of **R1–R11** plus the grep/Q&A
enforcement checklist. The **Three Decision Tests** are defined above under
`Core Definition`.

### Six Verdict Standards (R1–R6) — CHECK EVERY PR

| # | Question | SF-correct answer | Violation = BLOCK |
|---|----------|-------------------|-------------------|
| 1 | Can a `__c` field name appear in platform Go/TS code? | ❌ No | `AliCloud_Channel__c` hardcoded |
| 2 | Can ProfileId be used for behavior branching? | ❌ No | `ProfileId === 'system_admin'` |
| 3 | Where do default values / RecordTypes belong? | Config/metadata | Go const hardcodes "Open" |
| 4 | Can business enums be switch-case'd in code? | ❌ No | `{"NA":"...","EMEA":"..."}` |
| 5 | Can tenant tables enter platform SQL? | ❌ No | `FROM country_dictionary__c` |
| 6 | Who decides field writability? | FieldPermissions | Go map hardcodes field list |

### Five Judgment Standards (R7–R11) — CHECK EVERY PR

Q&A checklist (not pure-grep detectable; requires judgment + targeted code reading). Apply on every non-trivial PR, especially permissions, records, automation, metadata service, cross-replica state.

| # | Question | SF-correct answer | Violation = BLOCK |
|---|----------|-------------------|-------------------|
| 7 | Does generic engine code branch on a specific SObject API name? | ❌ No | `if objectName == "Lead"` or `map[string]X` keyed on SObject name in records/automation/permissions |
| 8 | Does platform code branch on a status string literal? | ❌ No | `if status == "Approved"`, `strings.Contains(rt, "master agreement")` |
| 9 | Does the diff invent a parallel SSoT for an SF-modeled fact? | ❌ No | New `implicitCRUDParent` column, Go cascade-children registry, or any substitute for `SharingModel` / `IsSharingParent` / `RecordType` / `FieldPermissions`. PR #1966 is the canonical counter-example |
| 10 | Does the same `(user, object, record)` decision have multiple read paths through different mechanisms? | ❌ No | Object-CRUD, record access, query RLS, seeder reading the same fact via different helpers. Multiple paths is the defect *even when answers currently agree* |
| 11 | Does a feature module own SObject-shape derived facts (cascade parent, FLS opacity, formula reference graph, hierarchy parent)? | ❌ No | A registry of SObject-shape facts inside `permissions/`, `records/`, `automation/`, or any feature module |

### Grep Test — RUN ON EVERY PR

```bash
grep -r "__c" backend/ frontend/ --include="*.go" --include="*.ts" | grep -v "_test.go"
grep -r "ProfileId ===" backend/ frontend/ | grep -v "_test.go"
grep -r '"NA"|"EMEA"|"APAC"|"Japan"' backend/ frontend/ | grep -v "_test.go"
grep -r '"Open"|"Prospecting"' backend/ frontend/ --include="*.go" | grep -v "_test.go"
```

Non-test-code hits = BLOCK.

---

## Key Files

Before investigating or fixing, classify: is this **code** or **data/metadata** (field defs, page layouts, picklist values)? Many "bugs" are metadata mismatches — read the data-vs-code skill.

### Mandatory-read triggers

Any change to `main` branch protection, CI workflow triggers or job definitions in `.github/workflows/`, path-filter configuration on a workflow that emits a required check, or review gating settings (e.g. `required_conversation_resolution`) → **read `.agent/skills/gh-branch-protection/SKILL.md` in full before acting**. Skipping = silent erosion of the merge gate.

### Reference table

| File | Purpose |
|------|---------|
| `docs/CODE_MAP.md` | Architecture analysis, priority list, module map |
| `.agent/skills/code-map-evo/SKILL.md` | Evo loop protocol (full skill definition) |
| `.agent/skills/coding-red-lines/SKILL.md` | **MUST-READ before any code change** — Six Verdict Standards (R1–R6), Five Judgment Standards (R7–R11), Grep Test, Greenfield Test, Admin Self-Service Test, Two-Path Consistency Test, common violation patterns |
| `.agent/skills/data-vs-code/SKILL.md` | **Read during any investigation** — classify code vs bootstrap data vs production-only data. Update when you discover new mappings. |
| `.agent/skills/data-change-spec/SKILL.md` | Idempotent data change spec format (markdown+SQL with YAML frontmatter) for `data-specs/` |
| `.agent/skills/data-spec-apply/SKILL.md` | Safety protocol for applying data specs — dry-run, rollback, idempotency verification |
| `.agent/skills/data-spec-compaction/SKILL.md` | Merging accumulated data specs (>20 specs or quarterly) |
| `.agent/skills/control-plane-setup/SKILL.md` | How to run and troubleshoot `cmd/setup apply`/`verify` for local and deployment readiness |
| `.agent/skills/e2e-test-policy/SKILL.md` | **Read before landing a PR that observably changes SObject CRUD behavior.** Owns the e2e-api case mandate, exemption categories (7-day hotfix deadline), required JSON shape, metadata-cache-aware setup patterns, and the scenario-promotion paper trail. CI signal at `.github/workflows/e2e-case-reminder.yml`. |
| `.agent/skills/browser-agent/SKILL.md` | Frontend runtime oracle — evidence standard, tool discovery, and routing to tool-specific skills. |
| `docs/design/script-layer-apex-positioning.md` | **MUST-READ for Apex/script/pack-executable comparisons** — separates current implementation from target design, defines NexusCRM script as Flow + Formula/Expression + Go-backed built-in functions/actions, and explains Apex product-role capability equivalence without Apex runtime compatibility. |
| `docs/test-infra/` | Regression strategy, issue-to-test policy, runbook, and current API/UI backlog documents for turning merged issue fixes into durable regression assets. |
| `.agent/skills/gh-branch-protection/SKILL.md` | **MUST-READ** before any change to branch protection, CI workflow triggers/jobs, path filters, or review gating on `main`. See "Mandatory-read triggers" above. |
| `.agent/skills/permission-bypass-review/SKILL.md` | Read whenever a diff calls `WithSecurityContext(SystemContext, ...)` near a target-record read, adds a GET handler whose response derives from record contents, or accepts a caller-supplied `objectApiName` / `entityType` alongside a record id. Six-question checklist for the RWO trifecta — RLS-bypass, wrong-object substitution, outcome side-channel — with the `ProjectChain` (#2026) case as the canonical worked example. |
| `.claude/commands/code-map-evo.md` | Slash command entry point for evo loop |
| `shared/pkg/constants/` | Generated constants (SSoT from `system_tables.json`) |
| `data-specs/` | Idempotent data change specifications (see `data-specs/README.md`) |

## Regression Asset Follow-Up

Fixing a high-risk issue is not the end of the delivery loop if the change adds
no durable regression protection.

After repairing a high-risk issue, agents must classify the fix against the
regression tiers documented in `docs/test-infra/`:

- fast API regression for narrow, CI-friendly invariants
- slower UI/runtime regression for browser-visible or metadata-rendered behavior
- scenario regression for multi-step business journeys

At minimum, the author must decide whether the fix:

1. adds or updates a fast regression asset now
2. adds or updates a UI/runtime or scenario asset now
3. records explicit debt in the current regression backlog documents

Do not silently stop at "PR merged" for user-visible business fixes, metadata-driven
runtime fixes, permissions/security fixes, setup/control-plane fixes, or
cross-object workflow fixes.

## Development Model (Quick Reference)

1. **Frontend = API specification** (verified against Salesforce, do not change)
2. **Backend = implementation** (missing routes = unfinished work, not bugs)
3. **NEVER change frontend API calls to accommodate backend**
4. **Frontend changes require Salesforce API v63.0 verification first**
5. **Backend changes must preserve frontend behavior**
