Skip to content

altessa-s/proto

Repository files navigation

proto

Shared protobuf schemas for the altessa-s ecosystem. Everything here is language-agnostic and reusable across services; nothing is tied to a single application. CI publishes generated bindings for six languages to dedicated per-language repositories (see Generated bindings).

Repository layout

io/altessa/
  badrequest/v1/        # error-detail payload for INVALID_ARGUMENT
  serviceinfo/v1/       # runtime service-introspection RPC
  type/v1/              # general-purpose value types (Contact, FileRef, …)
third_party/google/     # vendored googleapis protos (field_behavior + google.type.*)

io/altessa/<name>/v1/ holds an RPC contract plus any messages it needs. io/altessa/type/v1/ is a flat collection of small, reusable value types that have no Google-published equivalent. The directory path mirrors the proto package (io.altessa.<name>.v1), enforced by PACKAGE_DIRECTORY_MATCH. third_party/ vendors the handful of google/api and google/type protos the schemas import — see Vendored googleapis for the rationale.

Conventions

These apply uniformly to every schema in this repo.

  • proto3 with explicit optional on nullable scalar fields.
  • Field names are snake_case; messages and enums are PascalCase.
  • Each file declares package io.altessa.<name>.v1; matching its directory, plus consistent go_package, java_package, java_multiple_files = true, and an MIT license header.
  • Reuse Google common types before defining our own. Where a concept has a canonical google.type.* or google.protobuf.* message — LatLng, PostalAddress, Money, Date, Timestamp, Duration, … — we use it directly.
  • No string dates / int64 epochs. Wall-clock moments are google.protobuf.Timestamp; calendar dates are google.type.Date; durations are google.protobuf.Duration.
  • Field semantics via (google.api.field_behavior). We tag fields as REQUIRED / OUTPUT_ONLY / IMMUTABLE instead of shipping *Create / *Update companion messages — callers and codegen tools derive the create/update/FieldMask contracts from the annotations.
  • No custom validation extensions. Standard (google.api.field_behavior) is enough; documented invariants (e.g. "end_date MUST be ≥ start_date") are enforced by producers.

Design notes (deliberate AIP deviations)

  • id vs name (AIP-148). Value types in io/altessa/type/v1 use string id for stable identifiers of sub-entries (e.g. one element of a repeated Contact list). They are NOT resources in AIP-122/148 sense — there is no per-Contact CRUD RPC namespace, and no google.api.resource annotation. If a value type is ever promoted to a first-class resource with CRUD methods, that promotion will be done in a new package (io.altessa.<name>.v1) and will introduce a string name field per AIP-148; the original value type keeps its id shape unchanged.

Backwards-compatibility posture (AIP-180)

Until vX.0.0 is tagged on main, schemas under io/altessa/type/v1/ and io/altessa/<name>/v1/ are mutable and may be reshaped freely. After the first vX.0.0 tag, every package is frozen per AIP-180: no field removal, no type changes, no renumbering, no movement into/out of oneof. Wire-breaking evolution happens by adding a v2 package alongside v1.

Schemas

Package Files Purpose
io.altessa.badrequest.v1 io/altessa/badrequest/v1/ BadRequest / FieldViolation error-detail payload for google.rpc.Status with INVALID_ARGUMENT.
io.altessa.serviceinfo.v1 io/altessa/serviceinfo/v1/ ServiceInfo runtime metadata + ServiceInfoService.GetServiceInfo RPC for service introspection.
io.altessa.type.v1 io/altessa/type/v1/ General-purpose value types — see io/altessa/type/v1.

io/altessa/badrequest/v1

A structured error-detail contract for requests that fail input validation. It is designed to be carried as the detail payload of a google.rpc.Status with code INVALID_ARGUMENT, so a single failed call can report every offending field at once instead of bailing on the first.

A BadRequest carries a list of FieldViolations. Each violation has:

  • field_path — dotted/indexed path for humans ("user.address.street", "items[2].name").
  • message — human-readable explanation.
  • code — application-specific stable identifier (e.g. "too_short", "not_unique"), suitable for client-side i18n or branching logic.
  • field_path_components — the same path in structured form (field number, name, type, repeated index, map key), so clients can locate the field programmatically without parsing the string path.

The FieldType enum mirrors google.protobuf.FieldDescriptorProto.Type as an open proto3 enum, which keeps the schema usable from PHP and other generators that disallow proto2 closed-enum dependencies.

Server (Go)

Attach a BadRequest to an INVALID_ARGUMENT status using google.golang.org/grpc/status:

import (
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
    "google.golang.org/protobuf/proto"

    badrequestv1 "github.com/altessa-s/proto-gen-go/io/altessa/badrequest/v1"
)

func invalidArgument(violations ...*badrequestv1.FieldViolation) error {
    st := status.New(codes.InvalidArgument, "request validation failed")
    detail := &badrequestv1.BadRequest{FieldViolations: violations}
    st, err := st.WithDetails(detail)
    if err != nil {
        return status.Error(codes.Internal, "failed to attach error detail")
    }
    return st.Err()
}

// Example: reject a CreateUser request with two field problems.
func validateCreateUser(req *CreateUserRequest) error {
    return invalidArgument(
        &badrequestv1.FieldViolation{
            FieldPath: proto.String("user.email"),
            Message:   proto.String("must be a valid email address"),
            Code:      proto.String("invalid_format"),
        },
        &badrequestv1.FieldViolation{
            FieldPath: proto.String("user.age"),
            Message:   proto.String("must be >= 18"),
            Code:      proto.String("out_of_range"),
        },
    )
}

Client (Go)

Unpack the detail from any returned error:

import (
    "google.golang.org/grpc/status"

    badrequestv1 "github.com/altessa-s/proto-gen-go/io/altessa/badrequest/v1"
)

if st, ok := status.FromError(err); ok {
    for _, d := range st.Details() {
        if br, ok := d.(*badrequestv1.BadRequest); ok {
            for _, v := range br.GetFieldViolations() {
                log.Printf("field=%s code=%s msg=%s",
                    v.GetFieldPath(), v.GetCode(), v.GetMessage())
            }
        }
    }
}

io/altessa/serviceinfo/v1

A uniform runtime-introspection contract every service in the ecosystem can expose. A single RPC — ServiceInfoService.GetServiceInfo(GetServiceInfoRequest) returns (GetServiceInfoResponse) — returns identity (service_name, service_description, service_id), version (full_version plus structured SemanticVersion), build provenance (build_time, branch, commit, build_tags), liveness (start_time, uptime), distributed-leadership status (leader, leader_id), and arbitrary metadata for service-specific tags.

Every field on ServiceInfo is tagged OUTPUT_ONLY — clients never set them. Time-shaped fields use the canonical types:

  • build_time, start_timegoogle.protobuf.Timestamp
  • uptimegoogle.protobuf.Duration

Typical uses: a /info-style diagnostic endpoint on prod, correlating logs and metrics with the exact build that produced them, and discovering the active leader from any follower in a clustered deployment.

Server (Go)

import (
    "context"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/protobuf/proto"
    "google.golang.org/protobuf/types/known/durationpb"
    "google.golang.org/protobuf/types/known/emptypb"
    "google.golang.org/protobuf/types/known/timestamppb"

    serviceinfov1 "github.com/altessa-s/proto-gen-go/io/altessa/serviceinfo/v1"
)

type Server struct {
    serviceinfov1.UnimplementedServiceInfoServiceServer
    buildTime time.Time
    startedAt time.Time
}

func (s *Server) Get(ctx context.Context, _ *emptypb.Empty) (*serviceinfov1.ServiceInfo, error) {
    return &serviceinfov1.ServiceInfo{
        ServiceName: "billing-api",
        ServiceId:   proto.String("billing-api-7c9f"),
        Leader:      true,
        FullVersion: "1.4.2+build.873",
        SemanticVersion: &serviceinfov1.ServiceInfo_SemanticVersion{
            Major: 1, Minor: 4, Patch: 2,
        },
        BuildTime: timestamppb.New(s.buildTime),
        Branch:    proto.String("main"),
        Commit:    proto.String("a1b2c3d4e5f6"),
        StartTime: timestamppb.New(s.startedAt),
        Uptime:    durationpb.New(time.Since(s.startedAt)),
    }, nil
}

func register(g *grpc.Server, srv *Server) {
    serviceinfov1.RegisterServiceInfoServiceServer(g, srv)
}

proto.String comes from google.golang.org/protobuf/proto and is the idiomatic way to set proto3 optional scalar fields. timestamppb.New / durationpb.New build the well-known wrappers from time.Time and time.Duration respectively.

Client (grpcurl)

grpcurl -plaintext \
  -proto io/altessa/serviceinfo/v1/serviceinfo_service.proto \
  -import-path . \
  localhost:9090 \
  io.altessa.serviceinfo.v1.ServiceInfoService/GetServiceInfo

If the server registers gRPC reflection, drop -proto and -import-path and call the method directly.

io/altessa/type/v1

A flat collection of small, domain-neutral value types reused across services.

Message / Enum Use it for Notes
Contact Generic contact endpoint with a stable id and a typed value oneof (email / phone / social_handle) phone is a google.type.PhoneNumber, so country code and extension are preserved structurally. id lets parent messages address a single entry in a repeated Contact list for partial updates.
DatePeriod Calendar-date range, inclusive on both ends (billing periods, leave windows, …) Composed of two google.type.Date. Use google.type.Interval for wall-clock ranges.
DocumentRef User-facing document = file + display title/description Wraps FileRef.
FileRef Reference to a file held in object storage / CDN, with optional metadata Storage-agnostic — no bucket / backend identifier.
Gender Minimal biological-sex enum Intentionally limited to UNSPECIFIED / MALE / FEMALE. Extend in downstream schemas if richer identity is needed.
Label Short text tag with stable id and optional display color Color is google.type.Color.
LocationPrivacy Privacy / coarsening level applied to a user's geolocation before exposure Coarsening is the producer's responsibility.
MoneyRange Inclusive range between two monetary amounts Both bounds use google.type.Money and must share the same currency_code.
OrganizationType Coarse legal form: individual entrepreneur vs registered legal entity Jurisdiction-specific subtypes belong in domain schemas.

Go example

import (
    "google.golang.org/genproto/googleapis/type/date"

    typev1 "github.com/altessa-s/proto-gen-go/io/altessa/type/v1"
)

period := &typev1.DatePeriod{
    StartDate: &date.Date{Year: 2026, Month: 5, Day: 1},
    EndDate:   &date.Date{Year: 2026, Month: 5, Day: 31},
}

Generated bindings

Two parallel consumption channels are kept in sync: per-language GitHub repositories (existing) and BSR-managed packages (new).

Source of truth on BSR

The schema is published to buf.build/altessa-s/proto on every push to main / develop and every vX.Y.Z tag. Labels follow the source: branch pushes get a label matching the branch name; tag pushes get the tag label plus the rolling main label so consumers tracking :main follow released versions automatically.

Per-language packages

For each language CI publishes to a dedicated GitHub repository (existing, brand-aligned) and for Go / TypeScript / Java BSR additionally serves a generated SDK directly from the registry. Pick whichever channel matches your consumer ecosystem.

Language GitHub-hosted package BSR-generated SDK
Go altessa-s/proto-gen-gogithub.com/altessa-s/proto-gen-go buf.build/gen/go/altessa-s/proto/protocolbuffers/go (+ .../grpc/go)
TypeScript altessa-s/proto-gen-typescript — npm @altessa-s/proto-gen-typescript in GitHub Packages npm @buf/altessa-s_proto.bufbuild_es
Java altessa-s/proto-gen-java — Maven in GitHub Packages Maven build.buf.gen:altessa-s_proto_protocolbuffers_java
Swift altessa-s/proto-gen-swift — SwiftPM (messages only — see repo README) SwiftPM via Swift Package Registry: buf.altessa-s_proto_apple_swift (see Swift SwiftPM setup below)
PHP (classic) altessa-s/proto-gen-php — Composer (VCS); uses PECL grpc extension — (BSR has no Composer-managed PHP distribution)
PHP (RoadRunner) altessa-s/proto-gen-php-rr — Composer (VCS); uses spiral/roadrunner-grpc — (BSR has no Composer-managed PHP distribution)

Swift SwiftPM setup

Configure SwiftPM to resolve packages from BSR's Swift Package Registry (one-time per developer machine / CI image):

swift package-registry set https://buf.build/gen/swift --scope=buf

Then in Package.swift:

.package(id: "buf.altessa-s_proto_apple_swift", from: "<version>")

PHP / PHP-RR fallback via BSR archive

BSR has no Composer-registry for PHP. As a fallback, the generated output is downloadable as a versioned zip archive — useful when you want to bypass proto-gen-php / proto-gen-php-rr and pull straight from BSR (e.g. CI jobs that don't want a GitHub auth token):

# classic gRPC PHP
curl -fsSL -O https://buf.build/gen/archive/altessa-s/proto/community/protocolbuffers-php/<VERSION>.zip

# RoadRunner gRPC PHP
curl -fsSL -O https://buf.build/gen/archive/altessa-s/proto/community/roadrunner-server-php-grpc/<VERSION>.zip

<VERSION> follows the pattern <plugin-version>-<schema-commit-prefix>.<revision>, e.g. v5.3.0-a0178d2705f1.1 for the roadrunner-server-php-grpc plugin against the develop label snapshot a0178d27.... Browse the available versions on the module's SDK page. For day-to-day Composer use prefer altessa-s/proto-gen-php / altessa-s/proto-gen-php-rr — the archive route requires manual unpacking and a hand-written psr-4 autoload entry per consumer.

Versioning. Tag vX.Y.Z on main produces a SemVer release in every per-language repo and is also pushed to BSR as a fixed label. Pushes to main and develop publish branch snapshots in each per-language repo (main is fast-forward; develop is force-pushed from "main + freshly generated content" on each sync) and as floating labels on BSR.

Hand-edited files in target repos must live on main. Because develop is rebuilt on every sync from main + sync output, any manual commit made only on a target repo's develop (README, src/index.ts, composer.json, etc.) is silently lost on the next bot run. Land such edits on main first; the next sync brings them forward to develop automatically.

Build & lint

Five common-types files from googleapisgoogle/api/field_behavior.proto and google/type/{color,date,money,phone_number}.proto — are vendored under third_party/ and registered as a second buf module in buf.yaml. No buf dep update and no BSR token are required for lint or codegen; just run:

buf lint
buf breaking --against '.git#branch=main'

make proto          # all languages
make proto-go       # ephemeral; mirror of what CI publishes to proto-gen-go
make proto-java
make proto-swift
make proto-typescript
make proto-php
make proto-php-rr

Generated output lands under gen/<lang>/ (gitignored). buf lint and buf breaking run automatically in CI for every PR touching schema files. The vendored third_party/ module is excluded from both — its content is upstream's responsibility.

Vendored googleapis

The schemas reference google.api.field_behavior and google.type.{Color,Date,Money,PhoneNumber}. These are not bundled with protoc (only google.protobuf.* well-knowns are), and pulling them from buf.build/googleapis/googleapis requires a BSR token — inconvenient for offline development, CI without a BSR secret, and language toolchains (PHP, Swift) that don't speak BSR fluently.

Vendoring them under third_party/google/ keeps codegen hermetic for all six languages and removes BSR from the lint/gen critical path. Refresh by copying from the buf cache after any buf dep update run:

DIGEST=$(ls -1 ~/.cache/buf/v3/modules/b5/buf.build/googleapis/googleapis | tail -1)
SRC=~/.cache/buf/v3/modules/b5/buf.build/googleapis/googleapis/$DIGEST/files
cp $SRC/google/api/field_behavior.proto       third_party/google/api/
cp $SRC/google/type/{color,date,money,phone_number}.proto third_party/google/type/

If you add a new google/* import in a schema, vendor the new file the same way and add it to the Swift template's inputs: block (Swift needs each googleapis file as a primary input — see comment in buf.gen.swift.yaml).

License

MIT — see LICENSE.

About

Shared protobuf schemas for the altessa-s ecosystem

Topics

Resources

Stars

Watchers

Forks

Releases

Contributors

Languages