Skip to content

The map

Every repo in the ecosystem, and how they relate.

Tier 1: the emulators. Seven services, each in its own repo, each with its own release cadence and its own image on GHCR. Six are Azure services and compose as a family; snowflake is adjacent. Detail in The emulators.

Tier 2: the composition. azure-emulators runs no emulator of its own. It ships no binary, no image and no Go module. It is the neutral place that pins a certified combination (the bill of materials), wires the services together in one docker compose, and runs the chain test that no single member’s CI can run: proving that ARM validates entra’s tokens, that the advertised issuer matches the one its peers check, and that the images boot together in the right order.

Tier 3: the data product. Two shared repos plus a matrix of leaves. contoso-sources is the vendors and nothing else: three OpenAPI services and a Postgres change stream. contoso-data-product is the core: transform logic, data contracts, and the expected numbers every engine must reproduce. Each leaf carries only the per-platform idiom.

Tier 4: the platforms. The infrastructure that runs a product: compose files, emulator pins, vendor stacks, provisioning and connections. A platform contains no Contoso name and no product file. It takes PRODUCT=<path> and installs whatever you give it.

One pattern, and it is worth learning because it makes the matrix readable at a glance:

  • Platforms are <engine>-platform-<orchestrator>.
  • Leaf products are contoso-data-product-<engine>-<orchestrator>.
  • A platform runs the leaf with the matching suffix.

So fabric-platform-airflow3 runs contoso-data-product-fabric-airflow3. Neither names the other in code: the platform is handed a path, and the leaf pins the core by release wheel.

Emulators are <service>-emulator; two carry an older azure- prefix and are grandfathered, see the ontology.

Two phrases capture the separation, and they appear verbatim in the repos:

The platform installs the product and knows no Contoso.

It is a product, not a platform.

That separation is what makes the engine comparison honest. If each platform carried its own copy of the vendors and its own transform logic, a difference between Fabric and Databricks results would be unattributable. Two copies of a vendor is where a comparison dies.

RepoEmulatesIn the BOM
entra-emulatorMicrosoft Entra IDyes
arm-emulatorAzure Resource Manager + RBACyes
azure-keyvault-emulatorKey Vault data planeyes
azure-apim-emulatorAPI Managementyes
fabric-emulatorMicrosoft Fabricyes
databricks-emulatorAzure Databricks workspaceyes
snowflake-emulatorSnowflake accountno, adjacent
RepoRole
azure-emulatorsThe BOM, the family compose, the chain test, and the family parity report
RepoRole
contoso-sourcesThe four vendor systems, shared by every platform
contoso-data-productTransform logic, contracts, and the expected numbers
EngineOrchestratorLeaf productPlatform
FabricAirflow 3contoso-data-product-fabric-airflow3fabric-platform-airflow3
FabricNotebooks + Data Pipelinescontoso-data-product-fabric-notebook-pipelinesfabric-platform-notebook-pipelines
FabricBuilt-in Airflowcontoso-data-product-fabric-airflow-builtinfabric-platform-airflow-builtin
DatabricksDatabricks Jobscontoso-data-product-databricks-jobsdatabricks-platform-jobs
DatabricksAirflow 3contoso-data-product-databricks-airflow3databricks-platform-airflow3
SnowflakeSnowflake Taskscontoso-data-product-snowflake-taskssnowflake-platform-tasks
SnowflakeAirflow 3contoso-data-product-snowflake-airflow3snowflake-platform-airflow3

✅ built · ⬜ reserved, holding a README and a LICENSE

Reserved cells are real repos holding a README and a LICENSE. They exist so the shape of the matrix is visible before every cell is filled, and so a cell can be started without a naming debate.

Tokens carry iss = <entra login origin>/<tenant>/v2.0, and the peers validate that exact value. entra must advertise the origin they check. On the family compose network that origin is https://entra-emulator:8443, which is why the entra service sets PUBLIC_ORIGIN and every peer’s issuer setting repeats it verbatim.

Get it wrong and every call fails with an issuer mismatch. It is the single most common way these stacks break, and it is also the seam that makes the switch to a real tenant a configuration change: point the issuer variables at your tenant and nothing else moves.