The mapping service answers one question: given what my organization calls this thing, which repository holds its IaC? The identifier can be a project name, a subscription code, a product slug, or a full git URL — whatever the organization already uses. The service returns the clone URL and, when its catalogue knows them, the Terraform provider the identifier deploys with and the cloud scope it deploys into.
Only repo_url is guaranteed. terraform_provider and scope_id are
best effort, and null means "I do not know, ask the user" — never
"there is none". The wizard skips its provider and scope questions for
any field the mapper answers, so a confident wrong answer is worse than
null: the user is never prompted and never sees it. An implementation
returns null whenever its catalogue is not authoritative.
Contract: contracts/openapi/mapping.v1.yaml. Kumoss bundles no
implementation of it: repository mapping is an optional extension point
you implement yourself and point services.mapping.endpoint at.
Bearer token: the value of the variable named by
services.mapping.token_env, KUMOSS_MAPPING_TOKEN by default. The
service is optional and ships disabled
(services.mapping.enabled: false, endpoint: "").
It is internal-only. The browser never reaches it: the core forwards
requests through POST /api/v1/mapping/resolve.
Endpoints
Two paths.
| Endpoint | What it does |
|---|---|
|
Translates |
|
Liveness probe, answers |
Request
{"identifier": "my-project", "terraform_provider": "azure"}
Only identifier is required. additionalProperties is false.
| Field | Limits | Meaning |
|---|---|---|
|
1-1024 chars |
The business identifier to resolve. Free-form by design. |
|
enum, nullable |
The provider the caller already knows the deployment targets — one of
|
The wizard never sends terraform_provider: resolution runs at the
repository step, before a provider has been picked. The field exists
for contract completeness and for callers other than the wizard.
Response
{
"repo_url": "https://github.com/me/my-iac.git",
"identifier": "my-project",
"terraform_provider": "azure",
"scope_id": "0000aaaa-11bb-cccc-dd22-eeeeee333333"
}
| Field | Limits | Meaning |
|---|---|---|
|
1-2048 chars, required |
The |
|
1-1024 chars, required |
The request’s |
|
enum, nullable |
The provider the deployment targets. Echoes the request’s value when
one was sent; otherwise the implementation’s best-effort answer, or
|
|
1-1024 chars, nullable |
The cloud scope the deployment targets — Azure: subscription id, GCP:
project id, AWS: account id, OCI: compartment OCID, VMware: vSphere
datacenter name — as a best-effort answer, or |
additionalProperties is false on the response too, so a client can
rely on these four keys being the whole of it.
Status codes
| Status | Meaning |
|---|---|
|
Resolved. |
|
The body could not be parsed at the transport layer — malformed JSON,
non-UTF-8 bytes, no |
|
Missing or invalid bearer token. Carries a |
|
The token is valid but not authorized for this endpoint. |
|
The identifier is not in the catalogue. The contract reserves this
for a clean negative answer, distinct from a transient backend
failure, which is a |
|
Well-formed JSON that failed schema validation, including a
|
|
The downstream catalogue or CMDB returned an error. |
The 404-versus-502 split is the one design decision a catalogue
implementation should get right: it is what lets the core tell a user
"no such project" instead of "try again later".
How the core consumes the answer
The core calls the service from POST /api/v1/mapping/resolve and
surfaces a timeout as 504 and an unreachable sidecar as 502. Two
more answers are also 502: a body the core cannot parse, and a
terraform_provider outside the contract’s enum (the generated client
hands the raw string through, and the core refuses it rather than
letting it fail later), and a repo_url that is not an https:// URL.
There is no silent fallback once the sidecar is enabled.
When the sidecar is disabled (the shipped default) the core answers
with an identity passthrough and makes no call: repo_url and
identifier both echo the input, terraform_provider echoes whatever
was sent, and scope_id is null. Nothing is truncated and nothing is
guessed: sniffing azure out of a dev.azure.com URL would conflate
"hosted on Azure DevOps" with "deploys to Azure", and a guess is a
question the user never gets to correct. This is exactly right for
users who paste real HTTPS git URLs already. Any other input is echoed
unchecked too; the next request that uses it as repo_uri is rejected
with 422.
Providing your own implementation
A catalogue-backed implementation looks the identifier up in a CMDB,
Backstage, or a shared spreadsheet and returns the canonical IaC
repository, plus the provider and scope when the catalogue is
authoritative for them. The contract carries the result and says
nothing about where the lookup data lives. Set
services.mapping.enabled: true, point services.mapping.endpoint at
it, and set KUMOSS_MAPPING_TOKEN in core/.env; the core refuses to
boot when an enabled service has no endpoint or an empty token.
Conformance
The implementation-agnostic Schemathesis suite at
contracts/conformance/mapping/ runs against any implementation of
this contract:
cd contracts/conformance/mapping
uv sync
uv run pytest \
--service-url=https://mapping.your.example \
--service-token=$YOUR_TOKEN
--service-token is needed only if the implementation enforces
authentication.
The suite generates requests from the OpenAPI document and checks schema conformance of the responses. It cannot check that the identifiers your catalogue resolves are the right repositories, nor that a provider or scope it answers is true — that is what the implementation’s own tests are for.
Next steps
-
Core API for the core route that wraps this one.
-
the
servicessection of the configuration reference for enabling the sidecar. -
Make a request for which wizard questions a mapper’s answers skip.