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
21 changes: 11 additions & 10 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ This is the **primary** instruction file for AI coding agents. `AGENTS.md` and `

## What this repository is

The single source of truth for generic, domain-agnostic Protobuf contracts shared across Feather gRPC services. The schema in `proto/core.proto` is compiled into two published packages:
The single source of truth for generic, domain-agnostic Protobuf contracts shared across Feather gRPC services. The schema in `proto/feather/core/v1/core.proto` is compiled by `buf generate` into committed output under `gen/`, published as:

- **.NET**: `Feather.Contracts` NuGet package (built from `Contracts.csproj`).
- **PHP**: `feather/contracts` composer package, with generated classes committed under `php/`.
- **.NET**: `Feather.Contracts` NuGet package (`Contracts.csproj` compiles `gen/csharp`).
- **PHP**: `feather/contracts` composer package (autoloads `gen/php`).
- **Protobuf module**: `buf.build/feathertools/core` on the Buf Schema Registry.

It is consumed as a .NET dependency in `Feather.Grpc`, as a PHP dependency in the PHP contracts library, and by importing `proto/core.proto` directly into other `.proto` files.
It is consumed as a .NET dependency in `Feather.Grpc`, as a PHP dependency in the PHP contracts library, and by depending on the BSR module in other `.proto` files.

## Golden rules

Expand All @@ -21,21 +22,21 @@ It is consumed as a .NET dependency in `Feather.Grpc`, as a PHP dependency in th

## Editing the schema

After changing `proto/core.proto`:
After changing `proto/feather/core/v1/core.proto`:

1. Lint: `./bin/lint-proto.sh` (needs `brew install protolint`; config in `.protolint/.protolint.yaml`).
1. Lint: `buf lint` (config in `buf.yaml`, STANDARD rule set).
2. .NET classes regenerate automatically at build time via `Grpc.Tools` — run `./build.sh`.
3. Regenerate committed PHP classes: `./bin/grpc-php.sh` (builds `grpc-generator.dockerfile`, runs `generate-both.sh` in the container). Commit the updated `php/` output.
3. Regenerate committed classes: `buf generate` (remote plugins in `buf.gen.yaml`, managed mode) emits C# to `gen/csharp` and PHP to `gen/php`. Commit the updated `gen/` output. `buf build` only compiles the schema; use `buf generate` to emit code.

**Before opening a PR**, always run `./bin/grpc-php.sh` and commit the regenerated `php/` files so they stay in sync with `proto/core.proto` in git.
**Before opening a PR**, always run `buf generate` and commit the regenerated `gen/` files so they stay in sync with the schema in git.

### Proto naming conventions

- File names: `lower_snake_case`
- Messages / enums: `UpperCamelCase`
- Fields: `lower_snake_case`
- Enum values: `UPPER_SNAKE_CASE`
- Package stays `feather.core`; keep `csharp_namespace = "Feather.Contracts"` and `php_namespace = "Feather\\Contracts"`.
- Package is `feather.core.v1`; do NOT set `csharp_namespace` / `php_namespace` — Buf managed mode derives them (`Feather.Core.V1`, `Feather\Core\V1`).

## Build & tooling

Expand All @@ -48,7 +49,7 @@ After changing `proto/core.proto`:

- `net-tests.yaml` — .NET build & tests.
- `php-tests.yaml` — PHP lint / static analysis.
- `proto-lint.yaml` — protolint on the schema.
- `proto-lint.yaml` — `buf lint` on the schema.
- `net-publish.yaml` — publishes NuGet on a `MAJOR.MINOR.PATCH` tag push.
- `pr-check.yaml` — blocks fixup commits; shellcheck.

Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/bsr-publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: BSR Publish

on:
push:
tags:
- '[0-9]+\.[0-9]+\.[0-9]+'

jobs:
publish:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- uses: actions/checkout@v6

- name: Push to the Buf Schema Registry
uses: bufbuild/buf-action@v1
with:
token: ${{ secrets.BUF_TOKEN }}
push: true
10 changes: 6 additions & 4 deletions .github/workflows/proto-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ on:
- cron: "0 3 * * *"

jobs:
protolint:
buf-lint:
name: "Proto files linting"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Run protolint
uses: plexsystems/protolint-action@v0.7.0
- uses: bufbuild/buf-action@v1
with:
configDirectory: .protolint
setup_only: true

- name: Run buf lint
run: buf lint
28 changes: 0 additions & 28 deletions .protolint/.protolint.yaml

This file was deleted.

7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

<!-- There is always Unreleased section on the top. Subsections (Add, Changed, Fix, Removed) should be Add as needed. -->
## Unreleased
### Changed
- Distribute the schema via the Buf Schema Registry (`buf.build/feathertools/core`); added `buf.yaml` and `buf.gen.yaml` (managed mode).
- Moved `proto/core.proto` to `proto/feather/core/v1/core.proto` and bumped the package to `feather.core.v1`.
- Generated namespaces changed: .NET `Feather.Contracts` → `Feather.Core.V1`, PHP `Feather\Contracts` → `Feather\Core\V1`.

### Removed
- Removed the `csharp_namespace` / `php_namespace` proto options; Buf managed mode now derives them.

## 1.0.0 - 2026-07-30
- Initial implementation
6 changes: 2 additions & 4 deletions Contracts.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@
<RepositoryUrl>https://github.com/FeatherTools/grpc.contract.core.git</RepositoryUrl>
<PackageProjectUrl>https://github.com/FeatherTools/grpc.contract.core</PackageProjectUrl>
</PropertyGroup>
<!-- Compile the C# that buf generated (committed under gen/csharp). -->
<ItemGroup>
<Protobuf Include="proto/**/*.proto" GrpcServices="Both" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.Tools" Version="2.78.0" PrivateAssets="All" />
<Compile Include="gen/csharp/**/*.cs" />
</ItemGroup>
<Import Project=".paket\Paket.Restore.targets" />
</Project>
76 changes: 48 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@

> Core Protobuf contracts (generic, reusable message types) shared across Feather gRPC services — packaged for both .NET and PHP.

This repository is the single source of truth for the low-level, domain-agnostic contracts used across Feather. The Protobuf schema in [`proto/core.proto`](proto/core.proto) is compiled into:
This repository is the single source of truth for the low-level, domain-agnostic contracts used across Feather. The Protobuf schema in [`proto/feather/core/v1/core.proto`](proto/feather/core/v1/core.proto) is compiled into:

- a **.NET** library published as the [`Feather.Contracts`](https://www.nuget.org/packages/Feather.Contracts) NuGet package, and
- a **PHP** package (`feather/contracts`) with generated message, client and RoadRunner server classes under [`php/`](php/).
- a **.NET** library published as the [`Feather.Contracts`](https://www.nuget.org/packages/Feather.Contracts) NuGet package,
- a **PHP** package (`feather/contracts`) with generated message, client and RoadRunner server classes under [`gen/php/`](gen/php/), and
- a **Protobuf module** published to the [Buf Schema Registry](https://buf.build/feathertools/core) (`buf.build/feathertools/core`).

It is designed to be consumed in three ways:

1. As a **.NET dependency** in the `Feather.Grpc` library, providing the core message types.
2. As a **PHP dependency** in the PHP contracts library, providing the same core types.
3. **Directly in other `.proto` files**, by importing `proto/core.proto` and referencing the `feather.core` types.
3. **Directly in other `.proto` files**, by depending on the BSR module `buf.build/feathertools/core` and referencing the `feather.core.v1` types.

## Contracts

All messages live in the `feather.core` package ([`proto/core.proto`](proto/core.proto)):
All messages live in the `feather.core.v1` package ([`proto/feather/core/v1/core.proto`](proto/feather/core/v1/core.proto)):

| Message | Purpose |
| ----------------------- | ------------------------------------------------------------------------------ |
Expand All @@ -31,10 +32,10 @@ All messages live in the `feather.core` package ([`proto/core.proto`](proto/core
| `Box` | An `Instance` bound to a `Spot`. |
| `SerializedForChunking` | Wrapper (`bytes content`) for chunking large payloads in streaming gRPC calls. |

Generated namespaces:
Generated namespaces (derived by Buf managed mode from the `feather.core.v1` package):

- .NET: `Feather.Contracts`
- PHP: `Feather\Contracts` (messages) and `GPBMetadata\Proto\Core` (metadata)
- .NET: `Feather.Core.V1`
- PHP: `Feather\Core\V1` (messages) and `Feather\Core\V1\GPBMetadata` (metadata)

## Install

Expand All @@ -45,7 +46,7 @@ dotnet add package Feather.Contracts
```

```fsharp
open Feather.Contracts
open Feather.Core.V1

let error = Error(Name = "NotFound", Message = "Instance not found")
```
Expand All @@ -72,21 +73,31 @@ composer require feather/contracts:dev-main
```

```php
use Feather\Contracts\Error;
use Feather\Core\V1\Error;

$error = (new Error())->setName('NotFound')->setMessage('Instance not found');
```

### Import in another `.proto`

Add the BSR module as a dependency in your `buf.yaml`:

```yaml
version: v2
deps:
- buf.build/feathertools/core
```

Then import and reference the `feather.core.v1` types:

```proto
syntax = "proto3";

import "proto/core.proto";
import "feather/core/v1/core.proto";

message Envelope {
feather.core.CorrelationId correlation_id = 1;
feather.core.Error error = 2;
feather.core.v1.CorrelationId correlation_id = 1;
feather.core.v1.Error error = 2;
}
```

Expand All @@ -113,44 +124,53 @@ Common flags: `no-lint`, `no-clean` to skip the respective steps.

## Working with the Protobuf schema

`proto/core.proto` is the source of truth. After editing it:
[`proto/feather/core/v1/core.proto`](proto/feather/core/v1/core.proto) is the source of truth. After editing it:

1. **Lint** the schema:

```sh
./bin/lint-proto.sh # requires: brew install protolint
buf lint
```

2. **.NET** classes are generated automatically at build time by `Grpc.Tools` (see [`Contracts.csproj`](Contracts.csproj)) — just run `./build.sh`.

3. **PHP** classes are regenerated with the containerized generator (produces message, client `*Client`/`*Stub` and RoadRunner server `*Interface` classes):
3. **PHP** classes (and the C# reference output) are regenerated with Buf using the remote plugins in [`buf.gen.yaml`](buf.gen.yaml):

```sh
./bin/grpc-php.sh
buf generate
```

This builds [`grpc-generator.dockerfile`](grpc-generator.dockerfile) and runs [`generate-both.sh`](generate-both.sh) inside it. Generated PHP output lands in [`php/`](php/) and is committed to the repository.
This produces PHP message, client and RoadRunner server classes under [`gen/php/`](gen/php/) (namespace `Feather\Core\V1`) and the C# under [`gen/csharp/`](gen/csharp/); both are committed to the repository. `buf build` only compiles the schema to an in-memory image; use `buf generate` to emit code.

> **Before opening a PR**, run `./bin/grpc-php.sh` and commit the regenerated `php/` files so they stay in sync with `proto/core.proto` in git.
> **Before opening a PR**, run `buf generate` and commit the regenerated `gen/` files so they stay in sync with the schema in git.

4. **Publish** the module to the Buf Schema Registry:

```sh
buf registry login # first time only
buf push # publishes buf.build/feathertools/core
```

### Proto conventions

- File name: `lower_snake_case`; messages: `UpperCamelCase`; fields: `lower_snake_case`; enum values: `UPPER_SNAKE_CASE`.
- Keep types generic and domain-agnostic — this library holds only reusable primitives.
- Full rule set: [`.protolint/.protolint.yaml`](.protolint/.protolint.yaml).
- Enforced by Buf's `STANDARD` lint rule set (see [`buf.yaml`](buf.yaml)).

## Repository layout

```
proto/core.proto # Source-of-truth Protobuf schema
Contracts.csproj # .NET package (Feather.Contracts)
composer.json # PHP package (feather/contracts)
php/ # Generated PHP classes (committed)
build/ # FAKE build project (F#)
bin/ # Helper scripts (proto lint, PHP generation)
.github/workflows/ # CI: net-tests, php-tests, proto-lint, net-publish, pr-check
proto/feather/core/v1/core.proto # Source-of-truth Protobuf schema
buf.yaml # Buf module (buf.build/feathertools/core), lint & breaking config
buf.gen.yaml # Buf code generation (managed mode, remote plugins)
Contracts.csproj # .NET package (Feather.Contracts), compiles gen/csharp
composer.json # PHP package (feather/contracts), autoloads gen/php
gen/csharp/ # Generated C# classes (committed, compiled by Contracts.csproj)
gen/php/ # Generated PHP classes (committed, autoloaded by composer)
build/ # FAKE build project (F#)
.github/workflows/ # CI: net-tests, php-tests, proto-lint, net-publish, bsr-publish, pr-check
```

## Releasing

Publishing the NuGet package is triggered by pushing a semver tag (`MAJOR.MINOR.PATCH`), which runs the [`.NET Publish`](.github/workflows/net-publish.yaml) workflow. Update [`CHANGELOG.md`](CHANGELOG.md) and the `<Version>` in [`Contracts.csproj`](Contracts.csproj) before tagging.
Publishing the NuGet package is triggered by pushing a semver tag (`MAJOR.MINOR.PATCH`), which runs the [`.NET Publish`](.github/workflows/net-publish.yaml) workflow. Update [`CHANGELOG.md`](CHANGELOG.md) and the `<Version>` in [`Contracts.csproj`](Contracts.csproj) before tagging. Publish the Protobuf module to the BSR with `buf push`.
18 changes: 18 additions & 0 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: v2
clean: true
managed:
enabled: true # derive csharp_namespace / php_namespace from the package
plugins:
# C# messages + gRPC
- remote: buf.build/protocolbuffers/csharp
out: gen/csharp
- remote: buf.build/grpc/csharp
out: gen/csharp
# PHP messages + RoadRunner gRPC (official BSR plugin)
- remote: buf.build/protocolbuffers/php
out: gen/php
- remote: buf.build/community/roadrunner-server-php-grpc
out: gen/php
# future: TypeScript / Java — add one line each
inputs:
- directory: proto
10 changes: 10 additions & 0 deletions buf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: v2
modules:
- path: proto
name: buf.build/feathertools/core
lint:
use:
- STANDARD
breaking:
use:
- FILE
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
},
"autoload": {
"psr-4": {
"Feather\\Contracts\\": "php/Feather/Contracts/",
"GPBMetadata\\": "php/GPBMetadata/"
"Feather\\Core\\V1\\": "gen/php/Feather/Core/V1/"
}
},
"archive": {
Expand All @@ -29,6 +28,7 @@
"/proto",
"/tuc",
"/paket-files",
"/gen/csharp",
"*.csproj",
"*.sh",
"/paket.*"
Expand All @@ -48,7 +48,7 @@
"@composer normalize"
],
"lint": [
"vendor/bin/parallel-lint -j 10 ./php",
"vendor/bin/parallel-lint -j 10 ./gen/php",
"@composer validate",
"@composer normalize --dry-run"
]
Expand Down
Loading