diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3003de19..0db93b3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,11 +99,11 @@ jobs: mapfile -t SH_FILES < <(find . -name '*.sh' -not -path './.git/*' -not -path './.mise/*' -not -path '*/node_modules/*' | sort) [[ ${#SH_FILES[@]} -gt 0 ]] && shfmt -d -i 0 -ci "${SH_FILES[@]}" - # Lint proto files and check for breaking changes against main. - # buf breaking runs on PRs only (bufbuild/buf-action skips it on push to main - # where the change is already merged). FILE stability level (configured in - # proto/buf.yaml) is appropriate for v1alpha; switch to WIRE_JSON_COMPATIBLE - # when the API graduates to v1. + # Lint proto files, check for breaking changes against main, and run the + # AIP design linter. buf breaking runs on PRs only (bufbuild/buf-action skips + # it on push to main where the change is already merged). buf.yaml uses WIRE + # breaking detection with ignore_unstable_packages while the API is alpha; + # it switches to WIRE_JSON when the first stable (v1) package lands. proto: needs: changes runs-on: ubuntu-latest @@ -118,11 +118,38 @@ jobs: - uses: bufbuild/buf-action@v1 with: + version: 1.70.0 input: proto push: false pr_comment: false breaking_against: "https://github.com/${{ github.repository }}.git#branch=main,subdir=proto" + # AIP design lint (googleapis/api-linter) — the design-level rules buf's + # STANDARD ruleset doesn't cover. Mirrors the proto:api-lint mise task. + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Install api-linter + run: go install github.com/googleapis/api-linter/v2/cmd/api-linter@v2.3.1 + + - name: AIP design lint + working-directory: proto + run: | + buf build -o /tmp/cambeerfestival.pb + api-linter \ + --config .api-linter.yaml \ + --descriptor-set-in=/tmp/cambeerfestival.pb \ + --set-exit-status \ + cambeerfestival/festival/v1alpha/festival.proto \ + cambeerfestival/festival/v1alpha/producer.proto \ + cambeerfestival/festival/v1alpha/drink.proto \ + cambeerfestival/festival/v1alpha/catalog_service.proto \ + cambeerfestival/festival/v1alpha/drink_entry.proto \ + cambeerfestival/festival/v1alpha/drink_summary.proto \ + cambeerfestival/festival/v1alpha/my_festival_service.proto + # Analyze code in parallel with tests so builds can start sooner analyze: needs: changes diff --git a/mise.dev.toml b/mise.dev.toml index fe3ab1eb..4df3979e 100644 --- a/mise.dev.toml +++ b/mise.dev.toml @@ -16,14 +16,14 @@ [tools] watchexec = "2.5.1" -buf = "latest" +buf = "1.70.0" # --- Protobuf / OpenAPI (API contract is proto-first; see proto/README.md) --- -# buf and api-linter live here (dev-only) while the API design is still in flux. -# Move both to base mise.toml when the API stabilises and proto tasks enter CI. -# Move it to base mise.toml once the resource shapes and method signatures -# have stabilised and the linter output is expected to stay clean in CI. -"github:googleapis/api-linter" = "latest" +# buf and api-linter live here for local dev. CI runs the equivalent checks in +# the `proto` job (buf-action + api-linter via go install), so these versions +# are pinned to match CI. Move both to base mise.toml if the toolchain is ever +# needed by base (non-dev) tasks. +"github:googleapis/api-linter" = "2.3.1" [tasks."proto:lint"] description = "Lint the protobuf API contract (buf STANDARD ruleset)" @@ -86,6 +86,7 @@ buf build -o /tmp/cambeerfestival.pb api-linter \ --config .api-linter.yaml \ --descriptor-set-in=/tmp/cambeerfestival.pb \ + --set-exit-status \ cambeerfestival/festival/v1alpha/festival.proto \ cambeerfestival/festival/v1alpha/producer.proto \ cambeerfestival/festival/v1alpha/drink.proto \ diff --git a/proto/buf.yaml b/proto/buf.yaml index 4f121abd..e3bf9df7 100644 --- a/proto/buf.yaml +++ b/proto/buf.yaml @@ -7,9 +7,10 @@ lint: use: - STANDARD except: - # AIP-131/134: Get and Update return the resource itself, and Delete - # returns google.protobuf.Empty — both intentionally diverge from buf's - # "Response" / unique-response defaults. Google's own APIs do the same. + # AIP-131/134/164: Get, Update, and Delete all return the resource itself + # (Delete is a soft delete that returns the tombstone), so one response + # message is shared across methods and none match buf's "Response" + # naming default. Google's own APIs do the same. - RPC_RESPONSE_STANDARD_NAME - RPC_REQUEST_RESPONSE_UNIQUE breaking: @@ -40,9 +41,7 @@ breaking: # ignore_unstable_packages is true: alpha/beta packages are free to churn # (rename, move, restructure) without tripping breaking checks while the API # is still being shaped — only stable (v1+) packages are guarded. When the - # first stable (v1) package lands, switch breaking.use to WIRE_JSON_COMPATIBLE - # (superset of WIRE; also checks JSON field-name stability); the frozen v1 is - # then guarded while alpha stays exempt. + # first stable (v1) package lands, switch breaking.use to WIRE_JSON (superset + # of WIRE; also checks JSON field-name stability); the frozen v1 is then + # guarded while alpha stays exempt. ignore_unstable_packages: true - ignore: - - google/protobuf/empty.proto