Skip to main content

How to add a proto schema

All inter-layer contracts in Aucert are defined as Protobuf schemas in proto/. This guide covers adding new messages, modifying existing ones, and regenerating code.

Prerequisites

  • protoc compiler installed
  • Bazel configured (or standalone protoc plugins)

Current schemas

FilePurpose
proto/pipeline.protoMCPMessage envelope, TestRunRequest/Result, BugReport
proto/knowledge-graph.protoKGQuery, KGResponse, KGNode, KGEdge

Steps

Step 1: Edit or create the .proto file

// proto/pipeline.proto
syntax = "proto3";
package aucert.pipeline;
option java_package = "com.aucert.platform.domain.pipeline.model";

// Add new message types below existing ones
message TestSuiteConfig {
string suite_id = 1;
string name = 2;
repeated string test_scope = 3;
map<string, string> parameters = 4;
}

Step 2: Follow backward-compatibility rules

danger

Breaking changes to proto schemas can silently corrupt data. Follow these rules strictly.

Always safe:

  • Add new fields (with new field numbers)
  • Add new message types
  • Add new enum values
  • Add new oneof members

Never do:

  • Change an existing field number
  • Change a field's type
  • Remove a field that's in use (mark as reserved instead)
  • Rename a field (wire format uses numbers, not names, but it breaks generated code)
// Correct way to remove a field
message MyMessage {
reserved 3; // Field 3 was "old_field", removed in v2
reserved "old_field"; // Prevent accidental reuse of the name
string new_field = 4;
}

Step 3: Regenerate code

# Using Bazel (preferred)
bazel build //proto:all

# Generated output goes to schemas/generated/
# This creates code for: Kotlin, TypeScript, Python
caution

NEVER edit files in schemas/generated/. They are overwritten on every build. If you need custom behavior, wrap the generated types in your own code.

Step 4: Verify generated code compiles

# Kotlin
cd backend/platform && ./gradlew compileKotlin

# TypeScript
cd frontend/apps/console && pnpm tsc --noEmit

# Python
cd ml && uv run python -c "from schemas.generated import pipeline_pb2"

Step 5: Update consuming code

After regenerating, update any code that uses the modified messages:

  1. Backend — Update services/handlers in backend/platform/
  2. Frontend — Update API clients in frontend/apps/console/
  3. ML — Update any ML pipeline consumers in ml/

Step 6: Update context files

If your schema change introduces a new domain concept:

  • Add to .context/GLOSSARY.md
  • Update .context/ARCHITECTURE.md if it affects the pipeline

MCP message envelope

All inter-layer data flows through the MCPMessage envelope:

message MCPMessage {
string task_id = 1;
string source_layer = 2; // e.g., "generation"
string target_layer = 3; // e.g., "execution"
bytes payload = 4; // Serialized inner message
map<string, string> context_snapshot = 5;
double confidence_score = 6;
string trace_id = 7;
int64 timestamp_ms = 8;
}

The payload field contains the serialized inner message (e.g., TestRunRequest). Layers deserialize only the messages they understand.

Troubleshooting

Bazel build fails with "field number already used"

Two fields share the same number. Check your .proto file for duplicate field numbers. Each field in a message must have a unique number.

Generated Kotlin code has compilation errors

Ensure java_package option matches the expected package structure:

option java_package = "com.aucert.platform.domain.pipeline.model";

After fixing, clean and regenerate: bazel clean && bazel build //proto:all

TypeScript types don't match the proto schema

Check that the TypeScript proto plugin is configured correctly in the Bazel BUILD file. Regenerate and verify:

bazel build //proto:all
cd frontend/apps/console && pnpm tsc --noEmit

What's next