name: lfx-platform-architecture
description: >
Central explainer for how the LFX V2 platform components compose: Self Serve,
Goa services, NATS, JetStream KV, fga-sync, OpenFGA, indexer-service,
OpenSearch, query-service, access-check, Heimdall, Gateway API, Auth0, Helm,
and ArgoCD. Use when tracing platform write, read, access-check,
authorization, indexing, deployment, service-classification, or cross-repo
ownership flows. Fires on prompts like "how does FGA flow", "where does the
indexer fit", "what's the platform shape", "native service", "wrapper
service", "new V2 service", "Heimdall vs FGA", "what owns the OpenFGA
model", "how does query-service read resources", "how does access-check
work", "which repo owns deployed values", or "which service consumes index
messages". Do not use for V2 Go service coding conventions; the owning
repo's path-scoped <short-repo-name>-dev skill should attach after routing.
allowed-tools: Read, Glob, Grep
LFX Platform Architecture
Cross-cutting architecture explainer for how the LFX V2 platform fits together. Use this skill to understand component relationships, V2 service classes, and cross-repo ownership before handing implementation work to the owning repo.
Clean split:
/lfx-skills:lfx-platform-architecture: platform composition, V2 service classes, cross-service responsibilities, and handoff points.- Owning repo path-scoped
<short-repo-name>-devskill: Go coding conventions such as generated-code boundaries, logging, errors, request context, tests, formatting, and linting. - Owning repos: concrete implementation truth, contracts, subjects, payloads, chart values, and domain behavior.
When to invoke
- The user asks how platform pieces fit together.
- The user is tracing a request, write, read, authorization, access-check, or indexing flow.
- The user is classifying a V2 service as native, wrapper, proxy/consumer, or platform.
- The user is starting a new V2 service or deciding which repo owns a NATS subject family, KV bucket, indexer contract, FGA contract, query behavior, chart template, or deployed value.
- The user is changing an OpenFGA type or relation and needs the repo coordination order.
- The user is deciding which repo owns a platform contract.
- The user is routing deployment, chart, values, Auth0, ExternalSecret, or ApplicationSet work.
If the task is about coding inside an individual V2 Go service, first route to
the owning repo. The repo-local path-scoped <short-repo-name>-dev skill should
attach on relevant Go/service paths, alongside the repo-owned top-level docs/
files named by that repo's CLAUDE.md.
Platform shape
The V2 platform is a Goa-on-NATS service mesh fronted by Heimdall, authorized by OpenFGA, indexed into OpenSearch, and read through query-service.
Browser
-> lfx-self-serve Angular SSR
-> lfx-self-serve Express BFF
|
v
Gateway API + Traefik
-> Heimdall (authn + openfga_check authz per route)
|
v
+----------------------+ +-------------------+ +-------------------+
| V2 Go services | | query-service | | access-check |
| (Goa + NATS/KV, | | (HTTP read API | | (HTTP wrapper |
| platform contracts) | | over OpenSearch) | | over fga-sync) |
+----------+-----------+ +---------+---------+ +---------+---------+
| | |
v v v
NATS subjects + KV OpenSearch index OpenFGA tuples
lfx.index.* -> indexer-service -> OpenSearch
lfx.fga-sync.* -> fga-sync -> OpenFGA
lfx.access_check.request -> fga-sync (cached check)
Auth and deployment sit alongside the data path:
lfx-v2-auth-serviceis the NATS RPC abstraction over Auth0 and Authelia for identity, profile, and impersonation behavior.auth0-terraformowns Auth0 tenant configuration.lfx-v2-helmowns the shared platform chart and OpenFGA model template.lfx-v2-argocdowns deployed values, chart pins, image tags, ApplicationSets, previews, and promotion.
Service classes
Classify before choosing a work plan. The class determines which responsibilities exist and which repo-local docs should be present.
| Class | Quick sense | Living examples |
|---|---|---|
| Native resource service | Owns its data in NATS JetStream KV and exposes full CRUD through a Goa API. | lfx-v2-project-service, lfx-v2-committee-service |
| Wrapper resource service | Exposes LFX V2 APIs while an external system remains the source of truth. | lfx-v2-voting-service, lfx-v2-meeting-service, lfx-v2-mailing-list-service, lfx-v2-survey-service |
| Supporting application service | Owns feature-specific behavior or state that is not a generic V2 resource contract. | lfx-v2-email-service, lfx-v2-newsletter-service |
| Proxy or consumer service | Thin HTTP or NATS facade over platform plumbing. Does not own a resource type. | lfx-v2-access-check, lfx-v2-auth-service, lfx-v2-persona-service |
| Platform service | Provides shared platform capability consumed by other services. | lfx-v2-indexer-service, lfx-v2-fga-sync, lfx-v2-query-service |
Living examples are examples, not central owners of implementation truth. Concrete behavior belongs in the repo that owns the code.
Native resource services
A native resource service owns resource state directly in NATS JetStream KV.
Typical responsibilities:
- Goa HTTP API for resource reads and writes.
- One or more service-owned KV buckets.
- Optimistic concurrency for mutable resources when backed by KV revisions.
- Indexer publication for queryable resources.
- FGA publication for resources with their own access model.
- Service-owned chart templates and Heimdall
RuleSetentries for routes. - Health endpoints and graceful shutdown.
Native local docs should identify owned KV buckets, request/reply subjects, indexed resource types, FGA object types, and any deviations from the usual CRUD, optimistic-locking, or health/readiness shape.
Wrapper resource services
A wrapper resource service exposes an LFX V2 API while another system remains the source of truth.
Typical responsibilities:
- Goa HTTP API shaped for LFX V2 consumers.
- Translation between LFX V2 payloads and upstream payloads.
- Outbound auth to the upstream system.
- v2-to-v1 ID mapping when upstream resources use legacy Salesforce IDs.
- Indexer and FGA publication after successful upstream writes when the resource is queryable or access-controlled.
- Local KV only for mapping or cache needs, not as the resource source of truth.
Wrapper local docs should identify upstream ownership, LFX V2 to upstream field mappings, ID mapping requirements, local caches, emitted indexer/FGA messages, and upstream pagination, versioning, retry, or error quirks.
For ITX-specific plumbing such as OAuth2 M2M, lfx.lookup_v1_mapping, v1 KV
sync, and ITX_* environment variables, use
/lfx-skills:lfx-itx-integration.
Supporting application services
Supporting application services own feature-specific behavior that participates in the platform but is not a generic resource service.
Typical responsibilities:
- A small explicit public API, either HTTP or NATS request/reply.
- Service-owned state only when the feature needs it, such as Postgres rows or NATS KV tracking records.
- Clear local docs for the owned contract, consumed peer contracts, chart values, and deployment handoffs.
- No default assumption of Goa, indexer publishing, or FGA tuple emission.
Examples:
lfx-v2-email-serviceowns transactional email request/reply and engagement tracking. It does not render templates or publish indexer/FGA messages.lfx-v2-newsletter-serviceowns newsletter drafts, sent state, recipient resolution orchestration, open tracking, and analytics. It consumes query-service and email-service contracts.
Proxy, consumer, and platform services
Proxy and consumer services do not own resource data and usually do not publish indexer or FGA messages.
Architecture expectations:
- Keep the public or RPC surface small and explicit.
- Document the owned contract locally.
- Route tuple semantics, index document semantics, query behavior, identity provider behavior, or deployment values to the repo that owns them.
- Avoid applying native or wrapper resource-service assumptions unless the repo actually has those surfaces.
Examples:
lfx-v2-access-checkowns the HTTP access-check API. FGA tuple semantics live inlfx-v2-fga-sync.lfx-v2-auth-serviceowns auth/profile NATS RPCs. Auth0 tenant resources live inauth0-terraform.lfx-v2-persona-serviceaggregates persona summaries. It does not own KV resource state, indexer emission, or FGA emission.lfx-v2-indexer-service,lfx-v2-fga-sync, andlfx-v2-query-serviceown platform contracts consumed by resource services.
Write flow
- HTTP request hits Gateway API and Traefik.
- Heimdall authenticates through OIDC and authorizes through
openfga_checkrules in the service chart. - The owning service validates and performs the write.
- The owning service publishes platform messages as required by its resource
contract:
- index messages to
indexer-service - access messages to
fga-sync
- index messages to
indexer-servicewrites OpenSearch documents and emits domain events after successful indexing.fga-syncwrites or deletes OpenFGA tuples and updates its cache.
Service-class responsibilities live in this skill. Go coding conventions live
in the owning service repo's path-scoped <short-repo-name>-dev skill.
Read flow
- Self Serve or another consumer calls
query-servicefor indexed reads. query-servicereads OpenSearch.query-serviceasksfga-syncfor batched access checks over NATS.query-serviceremoves unauthorized resources before returning results.- Direct single-resource GETs go to the owning service and are authorized by Heimdall route rules.
Common failure owners:
- Missing document: owning service publish path or
indexer-service. - Missing access: owning service access emission,
fga-sync, or OpenFGA model. - Resource visible in API but absent from search: indexer or query-service path.
- Resource searchable but unauthorized: FGA tuple or access-check path.
Access-check flow
- Caller publishes a batched request on
lfx.access_check.request. fga-syncchecks its JetStream KV cache first.- On cache miss,
fga-synccalls OpenFGA and writes the result back to cache. - Caller matches replies by request token, not by reply order.
The generic access-check and tuple contracts live in lfx-v2-fga-sync.
The HTTP wrapper over access checks lives in lfx-v2-access-check.
OpenFGA and Heimdall coordination
Adding or changing an OpenFGA type or relation touches multiple repos.
| Layer | Owner repo | File or surface |
|---|---|---|
| OpenFGA authorization model | lfx-v2-helm |
charts/lfx-platform/templates/openfga/model.yaml |
| Endpoint authorization rules | Owning service repo | charts/<service>/templates/ruleset.yaml |
| Emitted access data | Owning service repo | service publisher code and local contract docs |
| Generic tuple handling and access checks | lfx-v2-fga-sync |
handlers and docs/fga-sync-contract.md |
| Deployed values and promotion | lfx-v2-argocd |
values, ApplicationSets, chart pins |
Coordination order:
- Update the OpenFGA model in
lfx-v2-helm. - Update the owning service's emitted access envelope.
- Update the owning service's Heimdall ruleset.
- Update the owning service's local FGA contract docs.
- Coordinate rollout with
lfx-v2-argocdif deployed values or chart pins are part of the change.
lfx-v2-fga-sync should not need type-specific code for a new resource type.
If it does, the envelope design is drifting.
Contract owners
| Concern | Owning repo |
|---|---|
| Product app, Angular SSR, Express BFF, shared package | lfx-self-serve |
| FGA tuple envelope, cache, access-check semantics | lfx-v2-fga-sync |
| Indexer envelope, OpenSearch document writes, indexing events | lfx-v2-indexer-service |
| Query API, OpenSearch read behavior, CEL, access filtering | lfx-v2-query-service |
| Access-check HTTP API | lfx-v2-access-check |
| Auth/profile/identity NATS RPC | lfx-v2-auth-service |
| Shared local platform chart and OpenFGA model | lfx-v2-helm |
| Deployed values, chart pins, image tags, ApplicationSets | lfx-v2-argocd |
| Local fixture loading and reset | lfx-v2-mockdata |
| Auth0 tenant control plane | auth0-terraform |
Use /lfx-skills:lfx and its repo map when deciding the primary repo for an
actual edit.
Cross-service handoff points
Read the owning repo before changing a contract shape. For exact current owner
files, use /lfx-skills:lfx and references/contract-ownership.md; this skill
keeps only the architectural ownership split.
| Concern | Owning repo |
|---|---|
| FGA envelope, tuple format, member operations, cache, access-check semantics | lfx-v2-fga-sync |
| Indexer envelope, OpenSearch document shape, event emission | lfx-v2-indexer-service |
| Query API, pagination over OpenSearch, filters, CEL, access filtering | lfx-v2-query-service |
| Cross-service Helm chart conventions | lfx-v2-helm |
| Shared platform chart and OpenFGA model | lfx-v2-helm |
| Deployed environment values, chart pins, image tags, ApplicationSets | lfx-v2-argocd |
Chart and deployment ownership
- Service-local chart templates and defaults belong to the service repo.
- Shared local platform composition and the OpenFGA model belong to
lfx-v2-helm. - Deployed environment values, chart pins, image tags, ExternalSecrets, and
promotion state belong to
lfx-v2-argocd.
What this skill is not
- Not a V2 Go coding rulebook. Repo-local path-scoped
<short-repo-name>-devskills control coding conventions when Go or service files are edited. - Not a repo-local contract. Read the owning repo's
CLAUDE.md, top-leveldocs/contract files, and only usedocs/agent-guidance/where the/lfx-skills:lfxrepo map explicitly lists it as a transitional owner path. - Not an ITX integration guide. Use
/lfx-skills:lfx-itx-integration. - Not a Self Serve implementation guide. Use
lfx-self-serve.
Handoff
After the platform flow and owners are clear, switch to the owning repo. The
owning repo's local instructions, top-level docs/ contract files, and
path-scoped <short-repo-name>-dev skill control implementation.