Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions components/lif/api_key_auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# `api_key_auth` — Component

Simple API-key authentication middleware for FastAPI. Validates `X-API-Key` headers against a configurable map of `key:client-name` pairs, and exposes the matched client name on `request.state.principal`.

Used by services that want bare API-key auth (no Cognito, no HS256 JWT) — the GraphQL API in particular. For the richer MDR-side middleware that handles three principal types, see [`mdr_auth`](../mdr_auth/).

## Public surface

```python
from lif.api_key_auth import ApiKeyAuthMiddleware, ApiKeyConfig

config = ApiKeyConfig.from_environment(prefix="GRAPHQL_AUTH")
if config.is_enabled:
app.add_middleware(ApiKeyAuthMiddleware, config=config)
```

The middleware no-ops when `<PREFIX>__API_KEYS` is unset, making local dev simple.

## Used by
- `bases/lif/api_graphql` — the GraphQL service's only auth path
20 changes: 20 additions & 0 deletions components/lif/auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# `auth` — Component

Lightweight HS256 JWT auth used by the Advisor and Example Data Source bases. Not the same as [`mdr_auth`](../mdr_auth/) — this one is older and simpler, with no Cognito or middleware-managed tenant routing. Demo-grade.

## Public surface

```python
from lif.auth.core import (
create_access_token, create_refresh_token, decode_jwt,
verify_token, get_current_user,
)
```

`create_access_token` / `create_refresh_token` mint short-lived access tokens and longer-lived refresh tokens. `verify_token` is a sync validator suitable for use as a FastAPI dependency. `get_current_user` is the FastAPI `Depends(...)` callable that extracts the authenticated username from a bearer token.

## Used by
- `bases/lif/advisor_restapi` — login + per-endpoint user resolution
- `bases/lif/example_data_source_rest_api` — `verify_token` behind `x-key` header auth

New services should default to `mdr_auth` instead unless they specifically don't want Cognito support.
19 changes: 19 additions & 0 deletions components/lif/composer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# `composer` — Component

Merges LIF fragments back into a `LIFRecord`. Used by the cache to assemble the final record returned to callers from many small fragment writes (one per data source, one per orchestration job).

## Public surface

```python
from lif.composer import compose_with_single_fragment, compose_with_fragment_list
```

| Function | Purpose |
|---|---|
| `compose_with_single_fragment` | Apply one `LIFFragment` to a base record |
| `compose_with_fragment_list` | Apply many fragments at once; order matters when they overlap |

Composition is path-driven: a fragment with `fragment_path = "person.Name"` is merged at that location. Path syntax follows the PascalCase/camelCase rules documented in [`docs/specs/data-model-rules.md`](../../../docs/specs/data-model-rules.md).

## Used by
- `components/lif/query_cache_service` — fragments arrive piecemeal; the composer stitches them into a complete record
25 changes: 25 additions & 0 deletions components/lif/data_source_adapters/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# `data_source_adapters` — Component

Adapter framework for pulling LIF data from heterogeneous source systems. The orchestrator picks an adapter by id, the adapter handles the source-specific API contract (auth scheme, pagination, response shape), and returns LIF fragments that the orchestrator merges into a record.

## Public surface

```python
from lif.data_source_adapters import LIFDataSourceAdapter, ADAPTER_REGISTRY, register_adapter
```

`LIFDataSourceAdapter` is the abstract base class. Subclasses live in sibling directories (one per adapter):

| Adapter id | Directory | Notes |
|---|---|---|
| `lif-to-lif` | [`lif_to_lif_adapter/`](lif_to_lif_adapter/) | Reads from another LIF GraphQL API |
| `example-data-source-rest-api-to-lif` | [`example_data_source_rest_api_to_lif_adapter/`](example_data_source_rest_api_to_lif_adapter/) | Reference impl that pulls from the bundled example data source |

External adapters can be registered via `register_adapter(adapter_id, adapter_class)`.

## Adding a new adapter

See [`docs/operations/guides/creating-a-data-source-adapter.md`](../../../docs/operations/guides/creating-a-data-source-adapter.md) for the class contract and design guidelines, or [`docs/operations/guides/add-data-source.md`](../../../docs/operations/guides/add-data-source.md) for the end-to-end tutorial.

## Used by
Pulled in by the orchestrator via configuration — the registry is consulted at adapter-dispatch time rather than by direct import from other components.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# `example_data_source_rest_api_to_lif_adapter` — Adapter

Reference adapter that pulls from the bundled [`example_data_source_rest_api`](../../../../bases/lif/example_data_source_rest_api/) base. Treat as a worked example for how to write a custom adapter against a real source API — copy this directory, rename, swap in your auth + URL conventions.

## Files

| File | What it does |
|---|---|
| `adapter.py` | `ExampleDataSourceRestAPIToLIFAdapter` — implements the `LIFDataSourceAdapter` contract |

## Registered as
`example-data-source-rest-api-to-lif` (see [`../README.md`](../README.md) for the registry).

## See also
[`docs/operations/guides/add-data-source.md`](../../../../docs/operations/guides/add-data-source.md) walks through cloning this adapter as the starting point for a custom data source.
16 changes: 16 additions & 0 deletions components/lif/data_source_adapters/lif_to_lif_adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# `lif_to_lif_adapter` — Adapter

Reads from another LIF GraphQL API. Used when one LIF deployment needs to pull learner data from a peer LIF deployment — typically in multi-org demos where org1's orchestrator fetches data hosted by org2 or org3.

## Files

| File | What it does |
|---|---|
| `adapter.py` | `LIFToLIFAdapter` — implements the `LIFDataSourceAdapter` contract |
| `graphql_query_all_fields_org2.graphql` | Pre-baked query template for org2 |
| `graphql_query_all_fields_org3.graphql` | Pre-baked query template for org3 |

The per-org `.graphql` files exist because schema field selection is hard to template dynamically against an evolving LIF data model; keeping the queries as text files makes them easy to inspect and edit. Generic adapters that target arbitrary LIF deployments will need to generate selections at runtime — that work isn't done here.

## Registered as
`lif-to-lif` (see [`../README.md`](../README.md) for the registry).
19 changes: 19 additions & 0 deletions components/lif/datatypes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# `datatypes` — Component

Core Pydantic models that flow through the LIF data plane. Every service that handles LIF records, queries, or jobs imports from here. Single source of truth for the wire shapes — bases don't define their own.

## Layout

| File | Contents |
|---|---|
| `core.py` | `LIFRecord`, `LIFPerson`, `LIFFragment`, `LIFQuery`, `LIFQueryFilter`, `LIFUpdate`, `LIFQueryPlan*`, `LIFPersonIdentifier(s)`, `LIFQueryStatusResponse`, `LIFQueryPlanPartTranslation`, `HealthCheckResponse`, `TargetTransformationDataModel(s)DTO` |
| `identity_mapping.py` | `IdentityMapping` |
| `mdr_sql_model.py` | SQLModel-style classes used by MDR persistence |
| `orchestration.py` | `OrchestratorJob`, `OrchestratorJobDefinition`, `OrchestratorJobRequest`, request/response wrappers |

## Naming convention

Models follow the PascalCase/camelCase split documented in [`docs/specs/data-model-rules.md`](../../../docs/specs/data-model-rules.md): entities (containers) are PascalCase, scalars are camelCase. Many models use `populate_by_name=True` with `alias="EntityName"` so they accept either case on input but normalize internally.

## Used by
Practically everything: every REST base, the cache and planner services, the orchestrator, the translator, and the MDR services. Changes here ripple widely; treat additions as a stable-API extension.
21 changes: 21 additions & 0 deletions components/lif/example_data_source_service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# `example_data_source_service` — Component

Sample data + business logic backing the [`example_data_source_rest_api`](../../../bases/lif/example_data_source_rest_api/) base. Provides a small fake person/course dataset that adapters can exercise locally without standing up a real SIS or LMS.

## Public surface

```python
from lif.example_data_source_service.core import (
user_info, users_info, users_info_filtered, courses_info,
)
```

| Function | Returns |
|---|---|
| `user_info(user_id)` | One sample person |
| `users_info()` | All sample persons |
| `users_info_filtered(filter)` | Subset matching the filter |
| `courses_info()` | Sample courses dataset |

## Used by
- `bases/lif/example_data_source_rest_api` — only consumer; this component exists to keep that base small and stub-able.
24 changes: 24 additions & 0 deletions components/lif/exceptions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# `exceptions` — Component

LIF-wide exception types. Services raise these instead of bare `Exception` so HTTP bases can centralize translation to status codes via `@app.exception_handler` registrations.

## Public surface

```python
from lif.exceptions.core import (
LIFException,
ResourceNotFoundException,
DataNotFoundException,
# ...
)
```

`LIFException` is the catch-all base. Sub-types convey common semantics (not-found, data-not-found, validation failure, etc.) so handlers can map them to 404 / 422 / 500 without `isinstance` chains in business code.

## Convention

When you add a new exception type, also wire its handler in any base that should respond differently to it. The translator base is a good template — see its `@app.exception_handler` cascade in `bases/lif/translator_restapi/core.py`.

## Used by
- Every REST base — handlers convert these to HTTP responses
- Service components — raise these from business logic
20 changes: 20 additions & 0 deletions components/lif/graphql_client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# `graphql_client` — Component

Authenticated HTTP client for calling the LIF GraphQL API. Wraps the boilerplate (auth header, error mapping, JSON shaping) into two functions so callers don't need to learn `httpx` semantics.

## Public surface

```python
from lif.graphql_client import graphql_query, graphql_mutation, GraphQLClientException
```

Both functions send `X-API-Key` from `LIF_GRAPHQL_API_KEY` (when set) as the auth header — see CLAUDE.md § "GraphQL API Key Authentication" for the server-side configuration.

| Function | Purpose |
|---|---|
| `graphql_query(...)` | Read-side query, returns parsed data |
| `graphql_mutation(...)` | Write-side mutation, returns parsed data |
| `GraphQLClientException` | Raised on transport or GraphQL-error response |

## Used by
- `components/lif/semantic_search_service` — calls GraphQL to fulfill MCP queries
14 changes: 14 additions & 0 deletions components/lif/identity_mapper_service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# `identity_mapper_service` — Component

Business logic for mapping a person's identifiers across source systems. Owns the rules for creating, listing, and deleting `IdentityMapping`s without knowing how they're stored.

## Public surface

```python
from lif.identity_mapper_service.core import IdentityMapperService
```

`IdentityMapperService` is constructed with an `IdentityMapperStorage` (the interface in [`identity_mapper_storage`](../identity_mapper_storage/)) and operates against it. This split lets the same business logic work over an in-memory store for tests and a SQL store ([`identity_mapper_storage_sql`](../identity_mapper_storage_sql/)) in production.

## Used by
- `bases/lif/identity_mapper_restapi` — instantiates one service per app + dispatches HTTP handlers to it
18 changes: 18 additions & 0 deletions components/lif/identity_mapper_storage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# `identity_mapper_storage` — Component

Storage interface for `IdentityMapping`s. Defines the abstract contract that the identity mapper service depends on, separate from any concrete database implementation.

## Public surface

```python
from lif.identity_mapper_storage.core import IdentityMapperStorage
```

`IdentityMapperStorage` is the abstract base class. Concrete implementations live in sibling bricks; the only one in tree is [`identity_mapper_storage_sql`](../identity_mapper_storage_sql/) (SQLAlchemy/MariaDB).

The split exists so the identity mapper service can be tested against an in-memory or fake implementation without spinning up a database — and so a future swap to a different backend (Postgres, Redis, etc.) wouldn't require touching service logic.

## Used by
- `bases/lif/identity_mapper_restapi` — declares the interface type for lifecycle injection
- `components/lif/identity_mapper_storage_sql` — implements the interface
- `components/lif/identity_mapper_service` (transitively, via the base)
26 changes: 26 additions & 0 deletions components/lif/identity_mapper_storage_sql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# `identity_mapper_storage_sql` — Component

SQL-backed implementation of [`identity_mapper_storage`](../identity_mapper_storage/). Uses SQLAlchemy against a MariaDB instance (deployed as `projects/lif_identity_mapper_mariadb/`).

## Layout

| File | Contents |
|---|---|
| `core.py` | `IdentityMapperSqlStorage` — the concrete `IdentityMapperStorage` impl |
| `model.py` | SQLAlchemy ORM model for the mapping table |
| `crud.py` | Low-level CRUD helpers used by `core` |
| `db.py` | Engine/session factory (`initialize_database`, `get_db_session_factory`, `dispose_db_engine`) |

## Public surface

```python
from lif.identity_mapper_storage_sql.core import IdentityMapperSqlStorage
from lif.identity_mapper_storage_sql.db import (
initialize_database, get_db_session_factory, dispose_db_engine,
)
```

`db.py`'s lifecycle helpers are called by the base's `lifespan` handler — engine initialization is per-app, not per-request.

## Used by
- `bases/lif/identity_mapper_restapi` — instantiates the SQL storage and threads it into the service
26 changes: 26 additions & 0 deletions components/lif/langchain_agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# `langchain_agent` — Component

Wraps LangChain/LangGraph into a `LIFAIAgent` purpose-built for the Advisor's chat experience: per-conversation memory, structured prompt templates, and an `ask_agent(task, query)` interface that maps high-level tasks ("load_profile", "continue_conversation", "save_interaction_summary") to the right prompt + tool chain.

## Layout

| File | Contents |
|---|---|
| `core.py` | `LIFAIAgent` — top-level interface (`setup`, `ask_agent`) |
| `helpers.py` | Prompt + chain construction helpers |
| `memory.py` | LangGraph memory wiring (`langmem`-backed) |
| [`prompts/`](prompts/) | Text-file prompt templates loaded at runtime |

Keeping prompts as plain text in `prompts/` (rather than f-strings in code) lets non-engineers tune wording without touching Python.

## Public surface

```python
from lif.langchain_agent import LIFAIAgent

agent = await LIFAIAgent.setup(config)
response = await agent.ask_agent("continue_conversation", user_message)
```

## Used by
- `bases/lif/advisor_restapi` — single consumer; this component exists to keep that base small.
15 changes: 15 additions & 0 deletions components/lif/langchain_agent/prompts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# `prompts/` — LangChain prompt templates

Text-file prompt templates loaded by [`langchain_agent`](../) at runtime. Kept as plain text so wording can be tuned without code changes or commits to Python files.

## Files

| File | Used when |
|---|---|
| `load_profile.txt` | Advisor's initial profile load — fires on `/start-conversation` |
| `continue_conversation.txt` | Each subsequent turn — fires on `/continue-conversation` |
| `summarize_interaction.txt` | Mid-conversation summary the agent uses to keep context bounded |
| `save_interaction_summary.txt` | Final summary written on `/logout` for future-session memory |
| `prompt_template_query.txt` | Base scaffold the other prompts inherit from |

Refer to `components/lif/langchain_agent/core.py` for the `task` → prompt mapping.
34 changes: 34 additions & 0 deletions components/lif/lif_schema_config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# `lif_schema_config` — Component

Centralized configuration and utility helpers for everything LIF services do with the schema: where to load it from, how to name GraphQL types from it, how to map XSD types into Python.

Replaces scattered `os.getenv("LIF_…")` calls across services with a single `LIFSchemaConfig` that knows how to read from the environment and validate.

## Layout

| File | Contents |
|---|---|
| `core.py` | `LIFSchemaConfig` — the main config class + `from_environment()` factory |
| `type_mappings.py` | XSD → Python type conversions used by schema generation |
| `naming.py` | Case conversion + GraphQL naming conventions (PascalCase / camelCase rules) |
| `openapi.py` | OpenAPI document structure helpers |
| Also exports | `DEFAULT_ATTRIBUTE_KEYS` — common attribute keys used by semantic search |

## Public surface

```python
from lif.lif_schema_config import LIFSchemaConfig, DEFAULT_ATTRIBUTE_KEYS

config = LIFSchemaConfig.from_environment()
config.root_type_name # "Person"
config.graphql_query_name # "person"
config.mdr_api_url # URL of MDR API
config.query_planner_query_url
```

## Used by
- `bases/lif/api_graphql`
- `bases/lif/semantic_search_mcp_server`
- `components/lif/query_cache_service`
- `components/lif/openapi_to_graphql`
- `components/lif/semantic_search_service`
26 changes: 26 additions & 0 deletions components/lif/logging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# `logging` — Component

The standard logger factory used across (almost) all LIF services. One module, one function, opinionated defaults: ISO timestamp with milliseconds, level name, logger name, message.

## Public surface

```python
from lif.logging import get_logger

logger = get_logger(__name__)
```

`LOG_LEVEL` env var (default `INFO`) controls verbosity. Format:

```
2025-10-09 14:33:21.123 INFO | my.logger | message
```

## When to use this vs. `mdr_utils/logger_config`

This component is the convention for non-MDR services. The MDR internals (`mdr_utils/logger_config.py`) use a slightly different format kept for historical compatibility — but new MDR endpoint code can use either since they share Python's `logging` module under the hood. New bases outside MDR should pull from here.

See [`docs/operations/guides/adding-a-new-microservice.md`](../../../docs/operations/guides/adding-a-new-microservice.md) for the full guidance.

## Used by
Most bases (api_graphql, advisor_restapi, query_cache_restapi, example_data_source_rest_api, semantic_search_mcp_server, orchestrator_restapi, translator_restapi, identity_mapper_restapi, query_planner_restapi) and many components.
11 changes: 11 additions & 0 deletions components/lif/mdr_client/resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `resources/` — Bundled OpenAPI schema

Ships the most recent known-good LIF OpenAPI schema as a static file. Used by [`mdr_client`](../) when `USE_OPENAPI_DATA_MODEL_FROM_FILE=true` (dev only) or when MDR is unreachable in deliberately-offline test setups.

## Files

| File | Purpose |
|---|---|
| `openapi_constrained_with_interactions.json` | Snapshot of the LIF V1.1 OpenAPI schema with interaction-mode constraints applied |

Kept in sync with MDR's V1.1 baseline; not the source of truth. Production services should always load schema from MDR, not from this file — see CLAUDE.md § "Schema Loading Pattern" for the no-silent-fallback policy.
Loading