Principle: One fact, one owner, one source of truth, one read path: L0 owns generic engines; L1 owns metadata assembly.

# CLAUDE.md - NexusCRM Agent Guide

## Repository Shape

NexusCRM is a Salesforce-compatible CRM platform with a Go backend, React
frontend, and TiDB runtime target. This repository is a public-core baseline:
greenfield startup must not depend on tenant-specific metadata, data, overlays,
or product/legal-entity names.

Use `README.md`, `go.mod`, and `package.json` to discover runnable commands.
Keep this file focused on rules that are easy to miss from code structure alone.

---

## Architecture Rule

NexusCRM is metadata-driven. Platform code is an interpreter, not a place to
author tenant business behavior.

The long-term product target is a clean application-neutral `L0`: the platform
does not assume that CRM is installed, and it does not predefine any `L1`
schema, metadata, lifecycle, UI, or reporting facts. Salesforce-compatible CRM
is the first `L1` app pack being implemented. Jira-like, ITSM, ERP, or other
apps must be expressible on the same `L0` substrate through their own packs.

> **Ownership decisions: cite the standard.** For any "does this belong in `L0`
> or `L1`?" question, [`docs/design/l0-l1-standard.md`](docs/design/l0-l1-standard.md)
> is authoritative. It gives the positive definition (L0 = the interpreter:
> metamodel + engines + seams), the ordered ownership tests (T0 standing → T5
> asymmetric default), and reproducible calibration anchors. On ownership
> questions it supersedes the prose in this section and the three disqualifiers
> of issue #2229; the material below stays as the day-to-day quick reference.

The platform has four layers:

- `L0` platform code: generic engines/interpreters.
- `L1` application/distribution metadata packs: installable product shape such
  as a Salesforce-compatible CRM pack. Standard objects, fields, layouts,
  picklists, flows, validation rules, and seed policy belong here when they are
  product defaults, not tenant decisions. Under the pure-platform target, `L1`
  is external pack payload, not platform-binary code.
- `L2` org/tenant metadata: admin-editable configuration and tenant-specific
  overlays of an installed `L1` pack, including custom objects, fields,
  permissions, validation rules, flows, layouts, approval processes, picklist
  changes, and tenant business policy.
- `L3` record data: user-created business rows.

Every fact belongs in one layer, has one owner, and is read through one
authoritative path.

The four commitments:

1. **Author decides layer.** Engineer-authored facts live in `L0` or `L1`;
   admin-authored facts live in `L2`; user-authored facts live in `L3`.
2. **`L0` is blind to `L2` instances.** Generic runtime may inspect schema
   shape, but must not encode specific `__c` fields, profile literals, picklist
   values, tenant tables, RecordType DeveloperNames, or ApprovalProcess names.
3. **One fact, one SSoT, one read path.** If Salesforce API `v63.0` already
   models a fact, reuse that representation instead of inventing a substitute.
4. **Exhaust reuse before inventing.** Use this order: Salesforce primitive ->
   existing platform primitive -> generic extension of an existing primitive ->
   new primitive.

If a business workflow is hard to express through existing metadata, treat that
as a generic platform substrate gap. Extend the metadata/runtime substrate; do
not implement the workflow as a one-off Go or TypeScript branch.

Use the multi-app test for every new substrate: would the same `L0` contract
host both the CRM pack and a Jira-like pack without changing platform code? If
not, the proposed contract is still application-shaped and belongs in `L1` or
in a transitional adapter, not in generic runtime.

### Pure Platform Boundary: Strong Reading

Honest acknowledgment: this is a stronger reading of the metadata-driven
principle than the repository currently operates under. The historical
tenant-quarantine pattern referenced by open-source boundary docs, the generic
`tenantoverlay` machinery, and the implicit "standard means
platform-binary-canonical" framing in `standard_objects_spec.json` all encode
the weaker reading. The public repo currently does not contain
`backend/internal/controlplane/tenants/**` payload, but the boundary model still
allows transition machinery and platform-binary standard metadata. Aligning the
repository to the stronger reading is a project-level architectural decision
that touches every team and every existing PR; it should not be made by
review-comment fiat on a single PR.

A platform that contains "what a Lead is" or "what an Order is" is not a pure
platform; it is a Salesforce-shaped CRM with an embedded engine. Whether
NexusCRM wants to be the former, the latter, or live in transition between the
two is the architectural question the team should pin down explicitly. Once
that is pinned, every existing rule, including the standard-SObject framing,
the four-layer model's `L1` definition, and the tenant-quarantine pattern,
should be re-stated in those terms.

Under the pure-platform target, platform ownership stops at generic substrate:
metadata storage, schema/runtime interpreters, formula/flow/validation engines,
security primitives, CRUD/query APIs, import/apply tooling, and drift checks.
CRM/Salesforce vocabulary is application scope, not platform scope. That
includes objects such as `Lead`, `Account`, `Opportunity`, `Order`, `Contract`,
and `Campaign`; their standard fields, picklists, layouts, actions, flows,
validation rules, lifecycle/status policies, and seed data; and tenant-specific
overlays of those facts. A Salesforce-compatible CRM should be an installable
metadata pack or distribution that runs on the platform, not facts compiled
into the platform binary.

The same rule applies to every future app pack. Jira-like concepts such as
`Issue`, `Project`, `Sprint`, boards, backlogs, workflow states, and issue
permissions are also `L1` facts. They are design validation examples for `L0`;
they must not be baked into platform code any more than CRM facts.

Use the strong reading as the target architecture for new work. Be explicit
when a change is only transitional and still follows the weaker model:

- **Current weak reading:** `L1` Salesforce-compatible standard metadata may
  ship in the platform binary; tenant-specific `L2` metadata must not.
- **Strong reading:** the platform binary ships only generic engines and
  substrate. Salesforce-shaped standard objects, fields, flows, validation
  rules, and picklists move to external `L1` app packs; tenant/org overlays
  and tenant business policy move to `L2` config applied through runtime/admin
  APIs.
- **Transition reading:** a PR may move behavior out of `L0` hardcoded engine
  branches into `L1` metadata without solving the full extraction of standard
  CRM shape from the binary, but it must name the remaining boundary work.

Examples:

- **PR #22 is a counterexample under every reading.** It centralizes Order
  activation policy in Go helpers, but the generic records engine still knows
  about `Order`, activation statuses, Contract status/date policy, and
  Order-specific field writes. That is application policy in `L0`.
- **PR #28 is also unacceptable under the strong reading, though it is a better
  counterexample than PR #22.** Moving Order activation from records-engine
  branches into standard validation rules and a standard before-save Flow
  removes the `L0` hardcoding problem. It still places CRM/Salesforce
  application facts at the wrong level if those rules/flows live under
  platform-binary `standard_*` metadata rather than an external `L1` CRM pack
  or `L2` tenant/org config.

Incomplete transitional work, including PR #28-style fixes:

- Moving an `Order` or `Lead` branch out of generic records/automation code and
  into `standard_validation_rules.json`, `standard_flows`, or
  `standard_objects_spec.json` fixes the `L0` hardcoding problem, but it does
  not satisfy the strong reading if those facts are not truly platform-generic.
- Validation rules that encode tenant business policy, customer-specific error
  messages, status allowlists, contract-window checks, or similar org behavior
  belong in `L2` tenant/org metadata, not platform-binary `L1`.
- Standard-field stamping or lifecycle semantics may be acceptable as `L1`
  app-pack metadata only when the team has confirmed they are
  Salesforce-standard product behavior. Otherwise they are also `L2`
  tenant/org metadata.
- A PR that lands under the transition reading should state what remains
  incomplete: which metadata still lives in the platform binary, why the team
  is temporarily accepting that wrong level, and what future extraction path
  would move it to an external `L1` Salesforce CRM pack or `L2` tenant/org
  config.

Implications for control plane boundaries:

- `tenantoverlay` and tenant-quarantine concepts must be redefined as generic
  import/apply substrate only. A tenant payload directory inside the platform
  repo is not the target architecture; tenant/org facts should live in external
  config and enter through runtime/admin APIs.
- `controlplane/packs` and `controlplane/packspec` must distinguish mechanism
  from payload. Code that parses, validates, applies, versions, reconciles, or
  checks drift for metadata can remain platform substrate. Files that define
  CRM/Salesforce shape, such as standard objects, fields, layouts, list fields,
  record types, validation rules, flows, actions, and status/picklist policies,
  are application-pack payload under the strong reading and should not be
  treated as platform-binary canonical facts.

Decision tests:

- **Greenfield:** would this work in a brand-new org with no tenant metadata?
- **Admin self-service:** can an admin change this business rule without a code
  change?
- **Two-path consistency:** does every caller read this fact through the same
  helper/source of truth?
- **Multi-app neutrality:** can the same substrate host CRM and a Jira-like app
  through different `L1` packs, with no app-object branches in `L0`?

---

## Public-Core Boundary

Do not add tenant-specific payload to this public repo. That includes:

- concrete tenant `__c` fields, custom objects, layouts, flows, validation
  rules, approval processes, RecordTypes, or picklist/status values
- tenant product, channel, customer, or legal-entity names
- tenant-specific SQL tables or bootstrap rows
- runtime branches that make greenfield behavior depend on tenant data

Generic platform mechanisms are allowed. For example,
`backend/internal/controlplane/tenantoverlay` may exist as machinery, but this
repo must not check in tenant overlay payload or require one for startup.

Historical extraction docs may mention tenant context as evidence. Runtime code,
bootstrap defaults, and public-core behavior must not depend on that context.

Historical cleanup also must not become permanent runtime logic. Clean obsolete
rows, columns, or tenant residue through an explicit one-time migration/runbook
or by removing the public-core artifact; do not add generic-engine special cases
that only exist to repair a past repository state.

---

## Coding Red Lines

Scope: backend platform code (`backend/internal/**`), frontend runtime code
(`frontend/src/**`), and L1 bootstrap/default metadata. Tests and fixtures are
exempt, but examples should still avoid tenant-shaped names unless they are
teaching generic suffix behavior.

Block these patterns:

- concrete `__c` field/object names in platform Go/TS or L1 bootstrap
- hardcoded ProfileId/Profile-name behavior
- hardcoded RecordType DeveloperName or ApprovalProcess DeveloperName behavior
- business enum/status/picklist branches such as lifecycle stage checks
- tenant table SQL such as `FROM some_object__c`
- field read/write allowlists in code; use FieldPermissions and
  ValidationRule metadata
- SObject-name branches inside generic engines, for example records,
  automation, permissions, RLS, or seeding
- parallel substitutes for Salesforce-modeled facts such as `SharingModel`,
  `RecordType`, `FieldPermissions`, or object/field shape facts
- multiple read paths for the same `(user, object, record)` decision
- feature modules owning derived SObject-shape registries that should live in
  metadata service helpers

Allowed in platform code when used as generic substrate, not CRM/Salesforce app
shape:

- generic metadata infrastructure fields such as `RecordTypeId`, `OwnerId`,
  `CreatedDate`, and `_System_*` infrastructure table fields
- standard permission keys such as `ModifyAllData`, `ViewSetup`, and
  other platform permissions not tied to a CRM application object
- `_System_*` infrastructure tables
- generic suffix handling for `__c`, `__r`, custom objects, and custom fields
- security hardening lists for secrets/tokens/password-like fields

Useful scan:

```bash
rg -n '"[A-Za-z0-9_]+__c"|FROM\s+[A-Za-z0-9_]+__c|ProfileId\s*[=!]=|Profile\s*[=!]=|DeveloperName\s*[=!]=' backend frontend shared \
  --glob '!**/*_test.go' \
  --glob '!frontend/node_modules/**' \
  --glob '!frontend/dist/**' \
  --glob '!frontend/package-lock.json'
```

Non-test hits need a clear generic-platform justification or removal.

---

## Salesforce Boundary

Salesforce API `v63.0` is the external product oracle. Frontend API usage is a
strong signal for the public contract; backend changes should move toward that
contract, not shrink the frontend to fit a backend gap.

NexusCRM may differ from Salesforce, but deltas must be explicit, justified,
metadata-driven, and verifiable. Visual presentation may differ more freely than
API shape, object/field/permission semantics, and admin authoring model.

**Contract verification is mandatory.** Before changing any handler under
`backend/internal/interfaces/rest/` or any UI API endpoint, follow the checklist
in [`docs/design/salesforce-v63-api-contract.md`](docs/design/salesforce-v63-api-contract.md).
That document records the binding wire-shape rules, the legacy → v63 error-code
mapping, and the catalog of known pitfalls (FLS leak in batch record access,
queryMore pagination, folder-shares envelope, formula validator engine, and
others). PRs that break v63 parity will be Codex-blocked.

---

## Script Layer

NexusCRM does not clone Apex. Its script/automation surface is metadata-first:
Flow metadata, Formula/Expression evaluation, and Go-backed built-in functions
or invocable actions. Treat Go-backed actions as generic platform capabilities
with metadata authoring, permissions, lifecycle, and observability; do not add
tenant business logic as hardcoded Go branches.

Per-tenant Go packages or checked-in Go scripts are not the public-core
extension model. Tenant behavior should be metadata that invokes generic
platform actions/functions.

When comparing Apex parity, classify a surface as implemented, generic extension
path, unsupported gap, or out of target. Do not treat "not Apex-compatible" as a
finding by itself, and do not treat "possible to implement in Go" as already
implemented.

---

## Refactor Discipline

Use higher discipline when a change flips a source of truth, stages a larger
refactor, combines backfill with destructive migration, or touches shared state
read by permissions, metadata, records, automation, query/RLS, or seeding.

Acceptable shapes:

- remove the wrong-shape piece and track the prerequisite, or
- land the new mechanism, switch all read paths, remove the old mechanism, and
  verify the cutover in one PR.

"Land now, fix the shape later" is not an acceptable shape.

---

## Working Standard

- Read the relevant code before designing the change.
- Prefer the existing local pattern over a new abstraction.
- Keep changes narrow and tied to the request.
- Do not revert user changes in a dirty worktree.
- Verify with evidence. Baseline commands are usually:

```bash
(cd backend && go test ./...)
(cd shared && go test ./...)
(cd frontend && npm run build)
git diff --check
```

`npm run build` rewrites `frontend/public/version.json`; revert that generated
change unless the version file is intentionally part of the task.
