> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Core Boundary Rules

> How the 12 cores (PF + 11 domain cores) talk to each other — what's allowed, what's forbidden, and the decision tree for cross-core work.

**Last Updated:** 2026-05-28
**Authoritative source:** [`constitution.md`](/governance/canonical/constitution) §1 (Architecture)

This page is the single-place answer to "can core X import from core Y?" — consolidated from `constitution.md` §1, `.cursor/rules/integration-patterns.md`, and [ADR-010](/architecture/decisions/index). If this page conflicts with the constitution, the constitution wins.

***

## The two non-negotiable rules

1. **Domain cores depend only on Platform Foundation (PF).** Never on other domain cores.
2. **Clinical (CL) is downstream only.** No other core may import from or depend on CL types or services. CL consumes from PM, RH, and PF; it does not export to them.

If your branch attempts a forbidden import, `npm run check-architecture` fails locally and CI blocks the merge.

***

## What goes where

| Layer                                                  | Examples                                                                            | Who may import from it                          |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------- | ----------------------------------------------- |
| **`packages/platform`** (PF)                           | identity, settings, audit, events, integration layer, shared types                  | All domain cores                                |
| **`packages/shared`**                                  | pure utilities, formatters, cross-cutting helpers                                   | All cores and PF                                |
| **`packages/core-{ce,cl,fa,fm,fw,gr,hr,it,lo,pm,rh}`** | domain logic, RLS-bounded entities, surface UI                                      | Nobody — only the host app                      |
| **`@/platform/clinical`** (PF)                         | clinical primitive types CL exports for everyone (encounter, problem, allergy DTOs) | Any core that needs to read clinical primitives |
| **`src/`** (root app)                                  | composes cores into the running Vite app                                            | n/a                                             |

The root path alias `@/*` → `./src/*` is the **root app only**. Cross-package imports use package names (`@encore-os/core-pm`, `@encore-os/platform`).

***

## Decision tree — "I need data from another core"

```
Need:        is the call read-only?
              ├── yes → consume via API contract (see below)
              └── no  → fire/listen for an event (see below)

  read path (you need to render or compute from another core's data):
    ├── value lives in PF (identity, audit, settings)          → import from @encore-os/platform
    ├── value is a clinical primitive (encounter, problem)    → import from @/platform/clinical
    ├── value is a domain entity of core Y                    → consume Y's published API contract via the Platform Integration Layer (PIL). Do NOT import core Y's types directly.

  write path (you need to cause something to happen in core Y):
    ├── core Y owns the action                                → publish an event on the PF event bus; Y subscribes
    ├── action is cross-cutting (notify, audit, log)          → use PF service directly (no event needed)
```

***

## The three sanctioned cross-core patterns

### 1. Platform Integration Layer (PIL)

PF exposes thin adapters that wrap a domain core's public surface. Other cores import from PF, never from the core. The PIL adapter is the only place that knows the shape of the foreign core. See [PLATFORM\_INTEGRATION\_LAYERS](/architecture/integrations/PLATFORM_INTEGRATION_LAYERS).

### 2. Event bus

Cores fire and listen for events through PF's event bus. Event names are typed in `KnownEventName`; contracts live in [EVENT\_CONTRACTS](/architecture/integrations/EVENT_CONTRACTS). For new events, follow [`encore-spec-research`](/development/SPEC_COMMAND_CHEATSHEET) flow.

### 3. API contracts

A core publishes an explicit API surface other cores may call (via PIL). Contracts live in [API\_CONTRACTS](/architecture/integrations/API_CONTRACTS). Versioning and breaking-change rules are in [`api-versioning-guide`](/development/api-versioning-guide).

***

## The encounter as the PM↔CL contract

The `pm_encounters` table is the canonical link between Practice Management (PM) and Clinical (CL). It is the only encounter entity — do not invent parallel `cl_encounters`, `appt_encounters`, etc. Both cores read the encounter via the PF clinical primitives layer. See [CL-PM-ENCOUNTER-TO-BILLING](/architecture/integrations/CL-PM-ENCOUNTER-TO-BILLING) for the full lifecycle.

***

## Worked examples

### A) FA needs the patient's primary insurance to post a charge

* ❌ Wrong: `import { fetchInsurance } from '@encore-os/core-pm'`
* ✅ Right: `import { getPrimaryInsurance } from '@encore-os/platform/billing'` — PIL adapter that wraps PM's published API.

### B) HR needs to know when a credential expires so it can fire a workflow

* ❌ Wrong: HR polls PM tables.
* ✅ Right: PM publishes `pm.credential.expired` on the event bus; HR subscribes. Contract in EVENT\_CONTRACTS.

### C) CL needs to display the patient's appointment slot

* ❌ Wrong: CL imports `Appointment` from PM.
* ✅ Right: PM exports an `EncounterSummary` clinical primitive into `@/platform/clinical`; CL imports from there.

### D) A new core (say "Pharmacy") wants to read clinical notes

* ❌ Wrong: Pharmacy imports CL types.
* ✅ Right: CL is downstream only. Pharmacy reads through `@/platform/clinical` (which CL has populated) or via an explicit CL→PIL adapter. CL must not depend on Pharmacy.

***

## How this is enforced

| Mechanism                           | What it catches                                                                       |
| ----------------------------------- | ------------------------------------------------------------------------------------- |
| `npm run check-architecture`        | direct core-to-core imports, `@/cores/*` paths from outside src, CL exports to non-PF |
| Eslint `import/no-restricted-paths` | per-package boundary rules (see each `packages/*/eslint.config.ts`)                   |
| Pre-commit                          | runs `check-architecture` on staged files                                             |
| CI gate                             | `check-architecture` blocks merges                                                    |
| Code review                         | constitutional review checklist explicitly asks about new imports                     |

***

## When you think you need an exception

You don't. Open a discussion in #engineering and bring the spec — the answer is almost always one of the three sanctioned patterns. Genuine architectural changes go through an ADR (see [`architecture/decisions/`](/architecture/decisions/index)).
