Anatomy of an extension
Every folder in a registry extension repository and what belongs in it — the reference to keep open while building.
A registry is one repository. This page is the map: what each folder is for, and what you must put in it. It is a reference, not a sequence — the order to do things in is Phase 1.
Everything here is real: the paths match the Farmer Registry and the National Social Registry as they are built today.
The repository
<domain>-registry/
├── <domain>-extension/ ← the Python package: your domain (see below)
├── docker/ ← thin Dockerfiles + the content your images carry
├── helm/openg2p-<domain>/ ← your Helm chart (wraps the platform chart)
├── test/ ← your field-specific sanity tests + the pin guard
├── scripts/ ← bump-rp-version.sh, uninstall-registry.sh
└── .github/workflows/ ← ~40 lines; all logic lives in openg2p/packaging<domain>-extension/
All of it
The only place your domain logic lives
helm/openg2p-<domain>/
A values overlay, plus any templates for things the platform has no concept of (analytics jobs, dashboards, maps)
Everything else comes from the pinned platform subchart
test/
Your Set 2 field tests only
The harness and generic tests are inherited. See Testing & the sanity suite
scripts/bump-rp-version.sh
Nothing — copy it
Moves the platform pin in the Dockerfiles and chart together
The extension package
<domain>-extension/
├── pyproject.toml
├── README.md ← REQUIRED — pyproject declares readme = "README.md"
├── LICENSE
└── src/openg2p_registry_<domain>_extension/
├── __init__.py ← version string + __variant__
├── app.py ← wires the extension into the platform app
├── config.py ← extension settings
├── register_domain/ ← the domain model (REQUIRED)
├── meta_data/ ← seed SQL: registers, UI, code lists (REQUIRED)
├── awe_meta_data/ ← approval-workflow seed SQL
├── templates/ ← DCI / message Jinja templates
├── score_compute/ ← optional: computed scores
└── ingestion_pipeline/ ← optional: inbound enrichersREADME.md is not optional. The pyproject.toml you copy declares readme = "README.md", and Hatchling validates it while generating metadata — so a missing file fails the Docker build, not at runtime, with:
The package installs under its own import name and is selected at runtime by REGISTRY_EXTENSION_MODULE. Do not alias it onto openg2p_registry_extensions in pyproject.toml — that was the previous mechanism and it prevents your extension from coexisting with the platform's reference extension in one image.
The mirror of that rule: the factories must still import the alias (openg2p_registry_extensions.register_domain.services), because the entrypoint installs it into sys.modules before any platform import runs. See Extensions Contract.
register_domain/ — the domain model
The heart of the extension. One set of files per register.
models/
SQLAlchemy ORM models — one file per register, plus enums.py. Each declares three tables: the register, its history, and its intake_form
9 files
schemas/
Pydantic mirrors of the models, used by the APIs
9 files
services/
G2PRegisterDomainService{Mnemonic} — the per-register behaviour the platform calls into. services/utils/ holds anything shared between them (validation helpers, recompute logic)
10 files
factory/
g2p_register_domain_factory.py and g2p_id_generator_factory.py — resolve mnemonic → your classes. Copy unchanged; the only thing you adjust is which services exist
2 files
id_generator/
Functional-ID generation, if your registers need it
1 file
The class names are a contract the platform resolves by register mnemonic — see Extensions contract for the exact names and methods, and Base models for what you inherit.
meta_data/ — the seed SQL
Applied to the registry database at install, in sorted path order. This is what turns your ORM tables into a working registry: registers, screens, code lists.
register-metadata/
The core: g2p_register_definitions, _schemas, _sections, UI tabs and tab-sections, intake-form equivalents, score definitions, documents. Start here
lookup-data/
Code lists — g2p_attributes and g2p_attribute_values (plus their _defaults variants)
registry-configurations/
Registry-wide settings: languages, themes, input mechanisms, VC configuration
data-models/
data_models.sql — the model definitions used by ingestion/outgestion
registry-inbound-message-rules/
Incoming templates, key paths, semantic patterns
registry-outbound-messages-templates/
Outgoing templates
awe-integration/
Binds registers to approval policies
Each metadata table is documented under Concepts → Register Metadata.
awe_meta_data/ — approval workflow
Numbered so they apply in dependency order, and written to the AWE database rather than the registry's:
Background: AWE integration.
templates/ — message rendering
Flat Jinja files, uploaded to MinIO by db-seed. Typically one per direction:
The top-level keys your outbound template emits are the consent scopes a partner can be granted — get them right or clamping silently returns {}. See Partner APIs.
Two key contracts you cannot infer from the templates themselves:
Outbound — child registers arrive under the snake_case of the register mnemonic (
AssistiveTechnology→assistive_technology).Inbound — the keys you emit are registry field names for the master register only. Everything else is keyed by the
section_mnemonicof the section that owns those fields. A key matching no section mnemonic is dropped silently.
Both are spelled out in Contracts that fail silently.
score_compute/ and ingestion_pipeline/ — optional
score_compute/services/— implementations ofG2PScoreComputeInterface, bound to a register by seed metadata rather than code. See Score computation framework.ingestion_pipeline/enricher_services/— enrichers that fill derived fields on inbound records. See Ingestion pipeline.
Omit either folder entirely if you do not need it.
What is not in the extension
A frequent source of confusion — these live in the repo but outside the package:
Sample data (demo records)
docker/db-seed/seed-data/*.json
Sample-data loader, image uploader
docker/db-seed/*.py — always yours. The platform's are written against the reference registry's tables and will crash-loop against yours; the entrypoint hard-fails if LOAD_SAMPLE_DATA=true and no variant loader is present
Reporting views, reporting.yaml
docker/db-seed/
Dashboards (the builder), maps
docker/dashboards/, helm/openg2p-<domain>/files/
Analytics jobs, maps ConfigMap, reporting-refresh CronJob
helm/openg2p-<domain>/templates/ — the umbrella chart owns no service templates, but it does own the analytics layer
Sanity field tests
test/sanity/
Repository guards (metadata consistency, pin lockstep)
test/ — see Contracts that fail silently
Domain translations
translation/domain.json — advisory copy only. Nothing reads it; the values reach the database via registry_languages.domain_translation in meta_data/registry-configurations/g2p_registry_languages.sql
Last updated
Was this helpful?