Ghillie now ships a YAML 1.2 catalogue describing programmes, projects,
components, repositories, and their relationships. Catalogue files are
validated with msgspec and exposed as a JSON Schema for external linters.
Validating a catalogue
The catalogue linter enforces YAML 1.2 semantics (strings like on remain
strings) and referential integrity between components.
- Generate schema and JSON artefacts from a catalogue file:
python -m ghillie.catalogue.cli examples/wildside-catalogue.yaml \
--schema-out schemas/catalogue.schema.json \
--json-out .cache/catalogue.json
- Validate against the JSON Schema with
pajv:
pajv -s schemas/catalogue.schema.json -d .cache/catalogue.json
- A non-zero exit code indicates structural errors, such as missing components in dependency lists or duplicate keys.
Example: Wildside
The catalogue example models Wildside as a multi-repository project:
leynos/wildside(core API) depends onwildside-engineand df12 shared libraries (ortho-config,pg-embedded-setup-unpriv,rstest-bdd).leynos/wildside-engineunderpins the core service and is blocked by shared configuration rollout.leynos/wildside-mockupmodels UI experiments and receives events from the core service.wildside-ingestionis markedlifecycle: plannedto represent work with no repository yet.
Noise controls ignore dependency bots and generated documentation paths, so the ingestion pipeline can focus on meaningful events.
Importing a catalogue into the database
The catalogue importer reconciles the YAML file into relational tables for estates, projects, components, repositories, and component edges. Imports run inside a single transaction: invalid catalogues fail fast and do not leave partial rows behind. Re-running the same commit is idempotent and will prune entries removed from the source catalogue.
Project noise filters, status preferences, and documentation paths are persisted alongside projects and repositories, so ingestion and reporting services can consume them without parsing YAML at runtime.
Operational note: existing deployments must add a JSON
documentation_pathscolumn to therepositoriestable before enabling this feature becauseBase.metadata.create_allwill not alter existing tables in place.
Example: load the example catalogue into a SQLite database using the asynchronous importer:
import asyncio
from pathlib import Path
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlalchemy import select
from ghillie.catalogue import CatalogueImporter, init_catalogue_storage
async def main() -> None:
engine = create_async_engine("sqlite+aiosqlite:///catalogue.db")
await init_catalogue_storage(engine)
session_factory = async_sessionmaker(engine, expire_on_commit=False)
importer = CatalogueImporter(session_factory, estate_key="wildside")
await importer.import_path(Path("examples/wildside-catalogue.yaml"), commit_sha="abc123")
asyncio.run(main())
In deployments that already run Dramatiq workers, use the
ghillie.catalogue.importer.import_catalogue_job actor. It accepts the
catalogue path, database URL, estate key, optional estate name, and commit SHA
so scheduling systems can enqueue work without importing Python modules.