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).
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.
These apply uniformly to every schema in this repo.
- proto3 with explicit
optionalon nullable scalar fields. - Field names are
snake_case; messages and enums arePascalCase. - Each file declares
package io.altessa.<name>.v1;matching its directory, plus consistentgo_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.*orgoogle.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 aregoogle.type.Date; durations aregoogle.protobuf.Duration. - Field semantics via
(google.api.field_behavior). We tag fields asREQUIRED/OUTPUT_ONLY/IMMUTABLEinstead of shipping*Create/*Updatecompanion messages — callers and codegen tools derive the create/update/FieldMaskcontracts 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.
idvsname(AIP-148). Value types inio/altessa/type/v1usestring idfor stable identifiers of sub-entries (e.g. one element of arepeated Contactlist). They are NOT resources in AIP-122/148 sense — there is no per-Contact CRUD RPC namespace, and nogoogle.api.resourceannotation. 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 astring namefield per AIP-148; the original value type keeps itsidshape unchanged.
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.
| 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. |
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.
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"),
},
)
}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())
}
}
}
}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_time—google.protobuf.Timestampuptime—google.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.
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.
grpcurl -plaintext \
-proto io/altessa/serviceinfo/v1/serviceinfo_service.proto \
-import-path . \
localhost:9090 \
io.altessa.serviceinfo.v1.ServiceInfoService/GetServiceInfoIf the server registers gRPC reflection, drop -proto and -import-path
and call the method directly.
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. |
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},
}Two parallel consumption channels are kept in sync: per-language GitHub repositories (existing) and BSR-managed packages (new).
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.
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-go — github.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) |
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>")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.
Five common-types files from
googleapis — google/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.
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).
MIT — see LICENSE.