Skip to content

feat(proto): myfestival v1alpha API contract, OpenAPI generation, and client codegen - #425

Merged
richardthe3rd merged 13 commits into
mainfrom
feat/proto-api-contract
Jun 13, 2026
Merged

feat(proto): myfestival v1alpha API contract, OpenAPI generation, and client codegen#425
richardthe3rd merged 13 commits into
mainfrom
feat/proto-api-contract

Conversation

@richardthe3rd

@richardthe3rd richardthe3rd commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Defines the online "my festival" API as Protocol Buffers under v1alpha, following Google's AIPs. Establishes the proto→OpenAPI generation pipeline and local client codegen tooling. Split out from #423.

Why v1alpha

The API is in active design — other "my festival" subsystems are not yet designed. v1alpha signals no stability guarantees (AIP-181); each subsystem graduates to v1betav1 independently. HTTP paths are /v1alpha/…, host is api.cambeerfestival.app (distinct from the static data.* CDN).

Proto contract (proto/cambeerfestival/myfestival/v1alpha/)

Five language-neutral files (go_package injected at generation time via buf managed mode — not written to source).

Resources — caller-scoped singletons per drink, absent-until-written

Resource Pattern Fields
Review .../drinks/{drink}/review optional star_rating, optional would_recommend
Bookmark .../drinks/{drink}/bookmark create_time only — presence = bookmarked
Note .../drinks/{drink}/note content (free text), update_time
Tasting .../drinks/{drink}/tasting optional pours, create_time, update_time

Caller identity is implicit in auth context — never in resource names (device IDs private, sign-in upgrade transparent).

Aggregates — public, read-only

Resource Pattern Fields
ReviewSummary .../reviewSummaries/{drink} rating_count, average_rating, response_count, recommend_count, recommend_rate
TastingSummary .../tastingSummaries/{drink} taster_count, total_pours

Service — MyFestivalService

Each resource gets Get/Update (upsert)/Delete/List. List RPCs scoped to a parent festival for bulk pre-load on app open. update_mask on Review lets star_rating and would_recommend be updated independently.

Deleted rating.proto and recommendation.proto (superseded by review.proto).

Tooling (dev-only — not in CI yet)

All proto tooling in mise.dev.toml. Both buf and api-linter move to base mise.toml when the API stabilises and proto tasks enter CI.

  • proto/buf.yaml — googleapis BSR dep, AIP-aware STANDARD lint ruleset
  • proto/buf.gen.yaml — gnostic OpenAPI remote plugin + buf managed mode (go_package_prefix)
  • proto/.api-linter.yaml — suppresses java options, delete-on-singleton (AIP-156), resource-pattern-singular for {drink} natural keys
  • buf lint, buf format, and api-linter all pass clean

Mise tasks

Task What it does
proto:generate proto → OpenAPI yaml (gitignored)
proto:clients:types openapi.yaml → Worker TS types (gitignored)
proto:clients:dart openapi.yaml → Flutter dart-dio client (gitignored)
proto:clients chains the two above
proto:lint / proto:format / proto:api-lint buf lint, buf format, api-linter

Nothing generated is committed

Proto files are the source of truth. All derived artifacts are gitignored and regenerated locally:

proto/*.proto  →  openapi.yaml  →  api-types.ts  (Worker)
                               →  packages/myfestival_client/  (Flutter)

Companion PR

Worker implementation: #426 (feat/worker-ratings-api) — needs updating to conform to /v1alpha/ paths and the unified resource model.


Generated by Claude Code

claude added 2 commits June 12, 2026 20:48
Define the online "my festival" API as Protocol Buffers following Google's
AIPs, as the source of truth for the ratings/recommendations endpoints. An
OpenAPI v3 doc is generated from it via a buf BSR remote plugin.

- proto/cambeerfestival/myfestival/v1: Rating/RatingSummary,
  Recommendation/RecommendationSummary resources and MyFestivalService with
  google.api.http annotations.
- Resource-oriented design: nested resource names, Update+allow_missing
  upsert (AIP-134), bodyless Delete (AIP-135), paginated List of summaries
  (AIP-158), field_behavior + resource annotations.
- buf.yaml (googleapis dep, AIP-aware lint) and buf.gen.yaml (gnostic OpenAPI
  remote plugin); buf added to the dev mise env with proto:lint / format /
  dep-update / generate tasks. See proto/README.md.

Contract only — buf lint/build and OpenAPI generation, plus reworking the
worker to conform, are pending network access to buf.build.
Run buf dep update to pin googleapis BSR dependency (buf.lock was
missing from the branch), then buf format -w to normalise whitespace —
collapsing multi-line option/field blocks onto single lines per buf's
default style. buf lint now passes cleanly.

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a proto-first contract for the “my festival” online API (ratings + recommendations), using Google AIP resource patterns as the source of truth and enabling OpenAPI v3 generation via buf for the Cloudflare Worker and HTTP clients.

Changes:

  • Introduces v1 protobuf resources (Rating/Recommendation plus aggregate *Summary) and a MyFestivalService with google.api.http REST mappings.
  • Adds buf module/lint configuration and a remote OpenAPI generator plugin configuration (plus a pinned buf.lock dependency).
  • Adds developer mise tasks to lint/format/update deps/generate OpenAPI and documents the workflow in proto/README.md.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
proto/README.md Documents the proto-first contract, resource model, and generation commands.
proto/cambeerfestival/myfestival/v1/rating.proto Defines Rating and RatingSummary resource schemas.
proto/cambeerfestival/myfestival/v1/recommendation.proto Defines Recommendation and RecommendationSummary resource schemas.
proto/cambeerfestival/myfestival/v1/my_festival_service.proto Defines service RPC surface + HTTP annotations + request/response messages.
proto/buf.yaml Configures buf module + STANDARD lint with documented AIP-motivated exceptions.
proto/buf.lock Pins googleapis dependency commit/digest for reproducibility.
proto/buf.gen.yaml Configures OpenAPI generation via BSR remote plugin.
mise.dev.toml Adds proto:* developer tasks for lint/format/dep-update/generate.

Comment thread mise.dev.toml
dir = "proto"
run = "buf generate"

# All tasks moved to mise-tasks/ for better maintainability and shellcheck/shfmt support:
Comment on lines +109 to +111
// If true (the default for this API), create the rating when absent (upsert).
bool allow_missing = 3 [(google.api.field_behavior) = OPTIONAL];
}
Comment on lines +172 to +174
// If true (the default for this API), create the answer when absent (upsert).
bool allow_missing = 3 [(google.api.field_behavior) = OPTIONAL];
}
Comment thread proto/README.md
Comment on lines +1 to +7
# API contract (proto-first)

The online "my festival" API (ratings + recommendations) is defined here as
Protocol Buffers following [Google's API Improvement Proposals](https://google.aip.dev)
(AIP). The proto is the source of truth; an OpenAPI v3 document is generated
from it for the (hand-written) Cloudflare Worker implementation and any HTTP
clients.
claude added 4 commits June 12, 2026 20:58
The caller is always implicit — device/user identity comes from the auth
context, not the URL. Replacing the ratings/{device} collection with a
singleton per (caller, drink) means:

- Device IDs never leak into resource names or logs
- URLs are unchanged when device tokens are replaced by user tokens
- Clients never need to know their own identity to construct a name

Pattern changes:
  festivals/{f}/drinks/{d}/ratings/{device}  →  festivals/{f}/drinks/{d}/rating
  festivals/{f}/drinks/{d}/recommendations/{device}  →  festivals/{f}/drinks/{d}/recommendation

Also: drop plural from singleton resource annotations; add DECLARATIVE_FRIENDLY
style; clarify allow_missing is always-true for singletons (retained for
AIP-134 generator compat); update all comments from "device" to "caller".

buf lint passes.

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
Install googleapis/api-linter via mise (github backend, dev-only while
the API design is in flux; move to base once shapes and signatures
stabilise).

Add proto:api-lint task: builds a buf descriptor set then runs
api-linter with --descriptor-set-in so imported googleapis protos
resolve without a separate proto-path.

Add proto/.api-linter.yaml config that suppresses three intentional
divergences from the default ruleset:
  - 0191 java-*: not a Java API
  - 0156 forbidden-methods: singletons expose Delete because ratings are
    absent-until-rated, not always-present
  - 0123 resource-pattern-singular: summary patterns use {drink} not
    {rating_summary} for readable URLs

Fix proto issues surfaced by the first run:
  - Remove style: DECLARATIVE_FRIENDLY (triggered etag/LRO/reconciling
    requirements we don't want; the singleton pattern stands alone)
  - Restore plural on Rating/Recommendation (required metadata even for
    singletons per AIP-0123)
  - Add doc comments to all request/response messages (AIP-0192)
  - Remove blank lines between section-separator comments and first RPCs
    (AIP-0192 only-leading-comments)

buf lint and api-linter both pass clean.

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
mise lock regenerated after adding github:googleapis/api-linter to the
dev env. Stale ubi:googleapis/api-linter entry pruned automatically.
mise.lock refreshed with current node patch version.

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
- Rename package v1 → v1alpha (API still in design; other my-festival
  features pending). HTTP paths flip to /v1alpha/. Promotes to v1beta/v1
  subsystem-by-subsystem as the design stabilises.
- Merge Rating + Recommendation singletons into a single Review singleton
  (festivals/{festival}/drinks/{drink}/review). Eliminates 4 round trips
  per drink card and keeps both callers signals in one resource.
- Add ListReviews for bulk pre-load on app open (parent=festivals/{f}).
- Add ReviewSummary aggregate resource with ListReviewSummaries for
  whole-festival list/grid views.
- Replace allow_missing (misleading no-op on singletons) with proper
  upsert semantics; update_mask now meaningful (star_rating vs
  would_recommend independently updatable).
- Fix resource type hostname: data.cambeerfestival.app/* everywhere.
- Add api-linter (googleapis/api-linter) to dev toolchain in mise.dev.toml.
  Suppressed: java options, delete-on-singleton (absent-until-written),
  resource-pattern-singular for reviewSummaries/{drink} natural key.
- Delete rating.proto and recommendation.proto (superseded by review.proto).

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
@richardthe3rd richardthe3rd changed the title docs(api): add proto-first AIP contract + buf/OpenAPI tooling feat(proto): add v1alpha myfestival AIP contract + buf/api-linter tooling Jun 13, 2026
claude added 3 commits June 13, 2026 08:00
…ostname

Add Bookmark, Note, and Tasting singleton resources to round out the
personal "my festival" state a caller can store per drink:

  Bookmark  festivals/{festival}/drinks/{drink}/bookmark
            Presence = bookmarked; create_time only field.

  Note      festivals/{festival}/drinks/{drink}/note
            Free-text tasting note (content, update_time).

  Tasting   festivals/{festival}/drinks/{drink}/tasting
            Tried-it log with optional pours counter; create_time +
            update_time. Paired with TastingSummary aggregate
            (taster_count, total_pours) for social "N people tried this".

Each resource gets Get/Update/Delete/List. All List RPCs are scoped to
a parent festival for bulk pre-load on app open.

Also: switch default_host and all resource type URIs from
data.cambeerfestival.app to api.cambeerfestival.app. The data.* host
is a static CDN; api.* will route to Workers for authenticated,
dynamic endpoints.

buf lint, buf format, and api-linter all pass clean.

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
Full pipeline: proto → OpenAPI → Worker TS types + Flutter Dart client.

## proto → OpenAPI (buf generate)

- Add `option go_package` to all v1alpha proto files (required by the
  gnostic-openapi BSR plugin for import resolution)
- `buf generate` now produces `docs/code/api/openapi/openapi.yaml`
  (899 lines, OpenAPI 3.0.3, server: https://api.cambeerfestival.app)

## OpenAPI → Worker TypeScript types

- Add `openapi-typescript` as devDep in `cloudflare-worker/`
- `proto:clients:types` task generates `cloudflare-worker/src/api-types.ts`
  (1051 lines of fully-typed path/operation/component interfaces)

## OpenAPI → Flutter Dart client (packages/myfestival_client)

- `proto:clients:dart` task downloads openapi-generator-cli 7.13.0 JAR
  (cached to ~/.cache/openapi-generator/) and generates a `dart-dio`
  package at `packages/myfestival_client/`
- Runs `dart pub get` + `dart run build_runner build` inside the package
  to produce the built_value `.g.dart` serialization files
- Root `.gitignore` updated: `!packages/**/*.g.dart` exception allows
  generated package serializers to be committed alongside their source
- `pubspec.yaml`: add `myfestival_client: {path: packages/myfestival_client}`
  as a path dependency (resolves cleanly with `flutter pub get`)

## Mise tasks

- `proto:clients` — chains types + dart
- `proto:clients:types` — openapi-typescript → api-types.ts
- `proto:clients:dart` — JAR download + openapi-generator + build_runner

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
…ions

Move go_package from individual .proto files into buf.gen.yaml managed
mode (go_package_prefix). buf injects the option at generation time only;
the proto files stay language-neutral.

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
@richardthe3rd richardthe3rd changed the title feat(proto): add v1alpha myfestival AIP contract + buf/api-linter tooling feat(proto): myfestival v1alpha API contract, OpenAPI generation, and client codegen Jun 13, 2026
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

claude added 4 commits June 13, 2026 08:16
buf was accidentally in base mise.toml, so CI would install it despite
having no proto tasks to run. Moved to mise.dev.toml alongside api-linter.
Both migrate to base when the API design stabilises and proto tasks enter CI.

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
packages/myfestival_client/ is a build artifact — regenerated from
openapi.yaml via `proto:clients:dart`. No need to commit ~8k lines of
generated Dart and lock file churn. Also removes the pubspec.yaml path
dep since the app doesn't consume the client yet.

Add packages/ to .gitignore. Wire it in when the client is actually used.

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
api-types.ts is generated from openapi.yaml via proto:clients:types.
Same rationale as packages/ — commit the source spec, not the derived artifact.

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
openapi.yaml is derived from the proto files via buf generate.
No generated artifacts committed — proto files are the source of truth.
Regenerate with: MISE_ENV=dev ./bin/mise run proto:generate

https://claude.ai/code/session_01WX7GbU19M9fh3tAfAxzeET
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Cloudflare Pages Preview

Your preview deployment is ready!

Preview URL: https://feat-proto-api-contract.staging-cambeerfestival.pages.dev

This preview will be automatically updated when you push new commits to this PR.

@richardthe3rd
richardthe3rd merged commit 4de4707 into main Jun 13, 2026
20 checks passed
@github-actions github-actions Bot mentioned this pull request Jun 12, 2026
@github-actions github-actions Bot mentioned this pull request Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants