A working example of CI/CD-driven semantic-layer provisioning. Merging to
main applies every YAML file in this repo to the FrontierOps platform;
pull requests dry-run-validate them first. No secrets are stored in this
repo — the workflow authenticates with GitHub's OIDC identity against a
trust binding configured on the platform.
orgs/
test/ ← the target organization's SLUG on FrontierOps
connector-slug/ ← the database connector's SLUG in that org
semantic-layers/
analytics.yaml ← the semantic layer's SLUG = the filename
The path encodes everything: orgs/<org-slug>/<connector-slug>/semantic-layers/<layer-slug>.yaml.
Which org each file goes to is decided by the org folder; which connector the
layer attaches to is decided by the connector folder. A folder for an org
outside the trust binding's grant fails with 403 for that file only.
The connector is a directory level so an org with several connectors keeps
each connector's layers separate (e.g. orgs/acme/snowflake/… and
orgs/acme/postgres/…). The connector must already exist in the org and be a
database-type connector; create it once in the dashboard (Connectors → Add)
before provisioning layers that reference it.
-
Platform (an org admin, on the parent org): Settings → Management API → New Trust Binding. Recommended pair for this repo:
- Apply: branch ref
refs/heads/main, scopes semantic layers read + write - Validate: no branch ref, scope semantic layers read only
The token exchange picks the right one automatically (most specific wins).
- Apply: branch ref
-
This repo: nothing to configure. The workflow file's
id-token: writepermission is the only "setup" — no secrets, no variables, no GitHub Apps. -
Create the database connector in each target org (dashboard → switch to that org → Connectors → Add), then name the connector folder in this repo to match its slug. In this sample, org
testhas a connector sluggedconnector-slug, so the folder isorgs/test/connector-slug/.
| Event | Job | Effect |
|---|---|---|
| Pull request | validate |
Each YAML dry-runs against POST …/validate; broken YAML fails the check with errors in the log. Nothing is written. |
Push to main |
provision |
Idempotent PUT …/by-slug/{slug} per file: unchanged YAML = no-op (version stays), changed = version bump, new file = created. |
Deletes are deliberate: removing a YAML file does not delete the layer.
Tip: mark the validate job as a required status check in branch protection
so an invalid semantic layer can never reach main.
401 invalid_grant— no binding matches (check the repo isowner/repoexactly, branch pin, binding enabled) or the OIDC token was already used.403on a PUT — that org isn't in the binding's grant, the binding was disabled, or the token lacks write scope.404 connector_not_found— the connector folder name doesn't match a connector slug in that org. Switch to the org in the dashboard, open Connectors, and confirm a database connector exists whose slug equals the folder name (connectors are per-org — one created in the parent org does not exist in a child).503 jwks_unavailable— transient GitHub keys outage; re-run the job.- Every applied change appears in Settings → Management API → the binding's Activity panel, with the GitHub run id and author.