Skip to content

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

  1. Read loader.yaml.
  2. Load archetypes.yaml (role presets + shared defaults).
  3. 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.
  4. Write to ES (skip existing unless dev mode).
  5. Invalidate Redis caches (profiles and accountability packs only).
  6. 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):

  1. Accountability keys match profile keys.
  2. Decision template IDs referenced in accountability exist.
  3. Task template IDs referenced in accountability exist.
  4. Signal policy IDs referenced in accountability exist in signal_policies.
  5. Signal policy source_models exist in domain models.
  6. No duplicate IDs across domains.
  7. No unresolved archetype references.
  8. Manifest has required fields (domain_id: string, dataspace: string, version: integer).
  9. Profiles have a capabilities dict.
  10. Decision templates have a template_id string.
  11. Task templates have a template_id string.
  12. Signal policies have a policy_id string.

Invariants

  1. UDS enforces ABAC. Schemas declare visibility; they do not enforce it.
  2. Events are append-only. The loader never updates or deletes events.
  3. Frozen means frozen. Immutable on all subsequent loads.
  4. AI assists, humans attest. Capabilities must not enable autonomous decisions.
  5. "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