Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/gate-openapi-conformance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: gate-openapi-conformance

# The OpenAPI spec is the FROZEN CONTRACT, and this gate measures the code
# against it. It never regenerates the spec from code — a spec derived from its
# implementation agrees with that implementation by construction and therefore
# cannot detect that the implementation drifted. That pipeline silently converts
# a contract into documentation.
#
# Nothing else in this repo closes this seam. Type-check, lint, unit tests and
# `helm template | kubeconform` are each individually happy with a service that
# implements an endpoint the contract never declared, or that quietly stopped
# implementing one a consumer generated a client for. gate-route-ownership
# checks WHICH SERVICE serves a path; this checks WHETHER THE PATH WAS AGREED.
#
# Triggers on both sides of the seam. A spec-only trigger would miss a route
# being added in code; a code-only trigger would miss a path being deleted from
# the contract. Either alone reproduces the blind spot.
on:
pull_request:
paths:
- 'services/*/openapi.yaml'
- 'services/*/src/**'
- 'governance/openapi-conformance-allowlist.txt'
- 'scripts/gate_openapi_conformance.py'
- 'scripts/__tests__/test_gate_openapi_conformance.py'
- '.github/workflows/gate-openapi-conformance.yml'
push:
branches: [master]
paths:
- 'services/*/openapi.yaml'
- 'services/*/src/**'
- 'governance/openapi-conformance-allowlist.txt'
- 'scripts/gate_openapi_conformance.py'

permissions:
contents: read

jobs:
openapi-conformance:
name: shipped code matches the frozen contract
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.12'

- name: Install PyYAML
run: pip install --quiet pyyaml

# The gate's own tests run FIRST and are weighted toward asserting that it
# FAILS on a violation. "It passed on the current tree" is not evidence a
# gate works — `sys.exit(0)` passes on the current tree too. If the self
# tests are red, the run below proves nothing and must not be trusted.
- name: Self-test the gate before trusting it
run: python -m unittest discover -s scripts/__tests__ -p 'test_gate_openapi_conformance.py' -v

- name: Check every service against its contract
run: python scripts/gate_openapi_conformance.py --repo .
18 changes: 16 additions & 2 deletions .github/workflows/harden-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,14 @@ jobs:
# clean tree. `claude-auto-pr.yml` was green for its whole life because
# every run took its early-exit path; the one time it had real work it
# failed. Passing is not evidence.
run: python -m unittest discover -s scripts/__tests__ -p 'test_*.py'
# Dependencies BEFORE the run, not after. Both self-test steps used to
# discover the whole suite with no install at all -- the `pip install`
# sat in the NEXT step, so anything needing PyYAML or jsonschema failed
# on import. That stayed invisible while every test happened to be
# stdlib-only, and surfaced the moment one was not.
run: |
pip install -q pyyaml jsonschema
python -m unittest discover -s scripts/__tests__ -p 'test_*.py'
- name: Identifier gate — contracts, parity, namespace, adoption (enforcing)
shell: bash
run: |
Expand Down Expand Up @@ -556,7 +563,14 @@ jobs:
shell: bash
# Same convention as gate-identifier's self-test step above: asserts the gate
# FAILS on each known-offender shape, not merely that it passes on a clean tree.
run: python -m unittest discover -s scripts/__tests__ -p 'test_*.py'
# Dependencies BEFORE the run, not after. Both self-test steps used to
# discover the whole suite with no install at all -- the `pip install`
# sat in the NEXT step, so anything needing PyYAML or jsonschema failed
# on import. That stayed invisible while every test happened to be
# stdlib-only, and surfaced the moment one was not.
run: |
pip install -q pyyaml jsonschema
python -m unittest discover -s scripts/__tests__ -p 'test_*.py'
- name: Vacuous-check gate (enforcing)
shell: bash
run: |
Expand Down
29 changes: 29 additions & 0 deletions governance/openapi-conformance-allowlist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Endpoints and identifiers deliberately outside the frozen OpenAPI contracts.
#
# METHOD /path an endpoint that exists on purpose but is not contract surface
# middleware NAME an identifier mounted with `.use()` that is middleware, not a router
#
# Every line here is a hole in the gate, so each one carries the reason it is
# not drift. An entry with no justification should be deleted, not extended.

# --- operational endpoints ---------------------------------------------------
# Liveness/readiness probes. Consumed by the kubelet, never by a generated
# client, and mounted OUTSIDE the service's `servers[].url` base on purpose so a
# probe still answers when the API base moves.
GET /health
GET /health/ready

# Swagger UI and the raw spec, self-served for humans. Documenting the endpoint
# that serves the documentation is circular.
GET /docs
GET /docs/openapi.json
GET /docs/openapi.yaml
GET /openapi.json
GET /openapi.yaml

# --- middleware that a static reader cannot distinguish from a router --------
# `app.use(API_BASE, graphCreate({ aggregate: ... }))` in billing-service and
# payment-service. Imported from @izzywdev/fuzefront-identity, so the extractor
# cannot walk into it; it enforces the lid/idMap graph-create rule and mounts no
# routes of its own.
middleware graphCreate
Loading
Loading