Cortex — Domain Loading
Cortex loads domain configuration from YAML schema files at startup. Domains define data models, user roles, governance rules, and UI behavior. A separate loader configuration controls which domains and demo data are active. Shared defaults in archetypes.yaml provide organization-wide baselines that every domain inherits. The pack schemas this spec loads are defined in component.cortex.pack-reference.
Terminology
| Term | Definition |
|---|---|
| Domain | A named configuration: one directory of YAML schema files under domains/. |
| Archetype | A role preset that profiles inherit. Three built-in: analyst → commander → principal. |
| Defaults | Organization-wide baselines for accountability, experiences, and templates. Defined in archetypes.yaml alongside role presets. |
| Demo | Pre-created objects and sample data for a domain. Dev mode only. |
| Mergeable key | A profile key that is deep-merged with the archetype value rather than replacing it. |
Schema Types
Seven schema types per domain:
| Type | Defines | Required |
|---|---|---|
| domain | What data exists — models, entities, joins | Yes |
| profiles | Who the users are — archetype, auth, ABAC visibility | Yes |
| accountability | What governance applies — routing, guardrails, attestation | Yes (or inherited from defaults) |
| experiences | How the UI looks — vocabulary, KPIs, monitor config | No |
| signal_policies | What alerts fire — thresholds, composite scoring, ES aggregation templates | No |
| decision_templates | What decisions look like — sections, evidence requirements | No |
| task_templates | What tasks look like — routing, SLA | No |
Assembly
Profile = Archetype defaults ──(mergeable keys deep-merge)──▶ Domain overrides
At startup: loader.yaml → archetypes + defaults loaded → domain schemas loaded → profiles exploded into ES → capabilities gate MCP tools. SSO roles are matched against profile auth.roles_any at request time to select the active profile.
Profile Field Schema
A profile document after archetype resolution and ES injection:
| Field | Source | Description |
|---|---|---|
profile_id |
YAML | Unique identifier (e.g. thebank_rm_v1) |
profile_name |
YAML | Human-readable name |
description |
YAML | Role description |
archetype |
YAML (removed after resolution) | Base archetype name |
capabilities |
Archetype + YAML (deep-merge) | Dict of capability_name: bool — gates MCP tools |
llm_tools |
Archetype + YAML (deep-merge) | List of LLM-callable tool names |
governance |
Archetype + YAML (deep-merge) | ABAC enforcement config (deny_mode, hide_unauthorized_fields) |
reports |
Archetype + YAML (deep-merge) | Report/export permissions |
search |
Archetype + YAML (deep-merge) | Search config (default_uds_model, allowed_models, preferred_dataspace) |
federation |
Archetype + YAML (deep-merge) | Federation config (enabled, federates) |
uds_source |
YAML | Dataspace source identifier |
link_field |
YAML | Entity linking field name |
auth |
YAML | SSO matching rules (roles_any, groups_any) |
uds.visibility |
YAML | ABAC visibility keys (matched against token AUTHORIZATION.READ markings) |
representative_user |
YAML | Demo display identity (display_name, email, department) |
domain_pack |
Auto-injected | Reference to parent domain doc ID |
dataspace |
Auto-injected | Dataspace name from manifest |
accountability |
Auto-injected | Reference to matching accountability doc ID |
experience_pack |
Auto-injected | Reference to experience doc ID |
Archetypes and Defaults
All shared configuration lives in packs/archetypes.yaml, with two top-level sections: archetypes: (role presets with capabilities, governance, tool access) and defaults: (organization-wide baselines for accountability, experiences, templates).
Role Archetypes
Each archetype is a complete role baseline. Capabilities are a dict mapping names to booleans. analyst carries explore/query/aggregate/view_schema/llm_chat/ai_assist/insight + evidence + signal_read + task capabilities plus a llm_tools list (~21 tools), governance (hide_unauthorized_fields: true, deny_mode: omit, min_group_size: 1), and reports. commander extends analyst, adding edition capabilities (edition_read, edition_manage, edition_attest, governance_read). principal extends commander, adding export_data and overriding edition_manage: false (audit separation).
Archetype Resolution
extends is additive: walk the chain to the root (e.g. principal → commander → analyst); reverse to base-first (analyst → commander → principal); deep-merge each level onto the accumulator; remove the extends key. Scenario-specific fields (review_routing, decision_template_ids, task_template_ids) never come from archetypes — they are domain-only.
Mergeable Key Set
When a profile specifies an archetype, only {capabilities, governance, search, federation, llm_tools, reports} are deep-merged with the resolved archetype. All other profile keys (auth, uds.visibility, link_field, representative_user) replace entirely. For mergeable keys: dict + dict → deep-merge (profile on top); non-dict (e.g. llm_tools list) → profile replaces. This lets profiles add (edition_read: true) or revoke (edition_manage: false) capabilities without restating the archetype.
Shared Defaults
The defaults: section provides organization-wide baselines merged into every domain: accountability (signals routing/escalation, insights require_entry_intent/require_subject, guardrails no_auto_decision/require_human_review/minimum_evidence_count: 1/require_rationale/ai_summary_requires_citation, attestation required: false); experiences (ui.nav.show_modules: [Home, Monitor, Explore, Investigations, Tasks], hide_modules: [Admin], standard entity page); decision_templates (freeze policy auto_freeze_on_attestation/freeze_all_pinned_blocks/hash_algorithm: sha256, attestation attester_must_differ_from_author: true); task_templates (required_context insight_id/description_required, completion must_add_evidence).
Default merge rules: accountability is merged per role (signals deep-merged under each role's signals; insights deep-merged with routing/template refs stripped from defaults; guardrails under each role's insights.guardrails; attestation under each role's attestation — domain wins). Templates are deep-merged as a floor under each domain template (deep_merge(defaults, template) — template wins). Experiences are deep-merged under domain experiences (domain wins; if a domain has no experiences file, defaults are used as-is). Domain-specific fields (review_routing, decision_template_ids, task_template_ids) are never injected from defaults.
Directory Layout
packs/
├── archetypes.yaml # Role presets + shared defaults
├── loader.yaml # What's on, what's off
├── domains/<name>/ # One dir per domain
│ ├── manifest.yaml # REQUIRED — identity & metadata
│ ├── domain.yaml # REQUIRED — data models
│ ├── profiles.yaml # REQUIRED — roles
│ ├── accountability.yaml
│ ├── experiences.yaml
│ ├── decision_templates.yaml
│ ├── task_templates.yaml
│ └── signal_policies.yaml
└── demos/<name>/ # Name must match domain dir
├── signals.json … tasks.json, keycloak_users.json
└── data/<federate>/<source>_<model>.json
Loader Configuration
packs/loader.yaml per domain: schemas: true (load domain schemas into ES) / false (skip entirely); demo: true (load demo objects + dataspace data; requires PACKS_DEV_MODE) / false (none). If loader.yaml is absent, all domains load with demo: false. Domains not listed in a present loader.yaml are not loaded (they must have schemas: true to be included).
Loading Lifecycle
- Read
loader.yaml. - Load
archetypes.yaml(role presets + shared defaults). - Per enabled domain: read YAML files; resolve archetype inheritance on profiles; merge default accountability into domain accountability per role; merge default experiences; merge default template values; validate cross-references; explode into individual ES documents.
- Write to ES (skip existing unless dev mode).
- Invalidate Redis caches (profiles and accountability packs only).
- If dev mode: load demo data for flagged domains.
Merge Rules
One path: archetype → profile (role config). One path: defaults → domain (accountability, experiences, templates).
| Case | Behavior |
|---|---|
| Dict + Dict | Recursive; override wins |
| List + List | Override replaces |
Value + None |
Key removed |
| Missing key | Inherited/default value preserved |
Write Semantics
| Mode | Existing doc | New doc |
|---|---|---|
PACKS_DEV_MODE off |
Skip (preserve) | store.create() |
PACKS_DEV_MODE on |
store.update() (overwrite) + invalidate cache |
store.create() + invalidate cache |
After writing, Redis caches are invalidated for profiles and accountability packs only (other subtypes are not Redis-cached). PACKS_DEV_MODE off → existing schemas preserved, no demo data ever; on → schemas overwritten, demo data per demo flag.
Explode
After merging, a domain is exploded into individual ES documents:
| Source | ES subtype | Count |
|---|---|---|
| Domain | domain |
1 |
| Profiles | profile |
N |
| Accountability | accountability |
N |
| Experience | experience |
0–1 |
| Signal Policies | signal_policy |
0–1 |
| Decision Templates | decision_template |
N |
| Task Templates | task_template |
N |
Auto-injected fields: profiles get domain_pack (parent domain doc ID), dataspace (manifest), accountability (matching doc ID by profile key), experience_pack (if the domain has experiences). Domain models are extracted from domain.yaml and injected into the manifest as models for validation. Accountability packs get applies_to.profile_ids. Every document is stamped with subtype, username: "system", version, uds.visibility.
Experience Pack Schema
Experience packs define UI vocabulary, navigation, monitor dashboard blocks, explore page config, and entity page layout (experience_pack_id, vocabulary map, ui.home/ui.nav, monitor.blocks, explore.welcome/suggested_actions, entity_page). Default experiences from archetypes.yaml provide ui.nav and entity_page baselines; domain experiences are deep-merged on top. Full schema in component.cortex.pack-reference#experience.
Signal Policy Schema
Signal policies define computed risk signals derived from dataspace data: each policy has policy_id, name, severity_default, source_model (must exist in domain.yaml models), and computation (type: threshold | boolean, field, thresholds[] with severity + reason, optional score_weight). A composite_score section defines weighted aggregation across policies (weights flat for boolean / per-severity for threshold, min_score_threshold). An optional es_aggregation_template defines the ES query pattern for computing signals at scale. Lifecycle semantics in component.cortex.signal-lifecycle#policies.
Dataspace Loading
Demo dataspace data is loaded from packs/demos/<name>/data/ into federation endpoints. Runs only when PACKS_DEV_MODE is set and the domain has demo: true. Federation routing: data files are organized by federation node directory for authoring clarity, but all batches are posted to FEDERATE_DOMAIN; cross-federate replication is handled server-side by fedai-rest, not the loader. Authentication: the dataspace loader authenticates via Keycloak password grant (not client_credentials) because federate endpoints require tokens with AUTHORIZATION claims and the atlasfl-storage role; credentials via DATASPACE_USERNAME / DATASPACE_PASSWORD. Idempotency: before posting, the loader checks whether data for each (source, domain) pair already exists on the remote federate and skips if so (cached per loading run).
Validation
Post-merge, pre-write. Warnings only (loading proceeds even if validation fails):
- Accountability keys match profile keys.
- Decision template IDs referenced in accountability exist.
- Task template IDs referenced in accountability exist.
- Signal policy IDs referenced in accountability exist in signal_policies.
- Signal policy
source_modelsexist in domain models. - No duplicate IDs across domains.
- No unresolved archetype references.
- Manifest has required fields (
domain_id: string,dataspace: string,version: integer). - Profiles have a
capabilitiesdict. - Decision templates have a
template_idstring. - Task templates have a
template_idstring. - Signal policies have a
policy_idstring.
Invariants
- UDS enforces ABAC. Schemas declare visibility; they do not enforce it.
- Events are append-only. The loader never updates or deletes events.
- Frozen means frozen. Immutable on all subsequent loads.
- AI assists, humans attest. Capabilities must not enable autonomous decisions.
- "No action" is a decision. Decision templates must support deliberate inaction.
Anti-Patterns
No flat JSON pack directories (the legacy loader is dead); no hardcoded domain names in code; no bypassing loader.yaml; no demo data without PACKS_DEV_MODE; no routing/template refs in archetypes; no direct ES writes outside the domain loader for domain configuration.
Depends on: component.cortex.intelligence, component.cortex.pack-reference
Realizes: product.pack, product.profile