BuildJobMessage Wire Format
sparki beginner 3 min read
ELI5
A build job on the queue is a small JSON envelope: who triggered it, what project, which commit, and an integer priority. The broker also stamps a unique message ID on the outside for delivery tracking.
Technical Deep Dive
Defined in internal/mq/producer.go as BuildJobMessage. Marshaled with encoding/json and published to QueueBuilds with content-type application/json (set by the producer) and a UUID MessageId AMQP property assigned per attempt.
Field Layout
packet0-71: "build_id (UUID string)"72-143: "project_id (UUID string)"144-183: "commit_sha (40-hex)"184-247: "branch (variable)"248-311: "triggered_by (variable)"312-319: "priority (int)"320-383: "timestamp (RFC3339)"384-511: "metadata (object, optional)"(Bit layout above is illustrative — the wire format is JSON; widths show typical byte ranges, not fixed offsets.)
JSON Schema (Effective)
| Field | Type | Required | Notes |
|---|---|---|---|
build_id | string (UUID) | yes | Engine-generated, becomes the build row PK |
project_id | string (UUID) | yes | FK into projects table |
commit_sha | string | yes | 40-hex per build-result.json schema |
branch | string | yes | Git ref short form |
triggered_by | string | yes | User ID, system actor, or webhook:github |
priority | int | yes | AMQP message priority (0–9 typical) |
timestamp | RFC3339 string | yes | Marshaled from time.Time |
metadata | object | no | Free-form; omitempty |
Example
{ "build_id": "4c8c7a8e-2c3a-4f4f-9f88-7c9a8e8a6b2d", "project_id": "11111111-2222-3333-4444-555555555555", "commit_sha": "abcdef0123456789abcdef0123456789abcdef01", "branch": "main", "triggered_by": "user:alex", "priority": 7, "timestamp": "2026-05-05T09:30:00Z", "metadata": {"source": "webhook:github"}}AMQP Envelope Properties
content-type: application/jsonmessage_id: UUID generated per publish attempt (uuid.New().String()inpublishWithRetry); persists across retries within the same attempt.priority: copied fromBuildJobMessage.Priority.delivery_mode: persistent (set by the producer to survive broker restart).
Key Terms
- commit_sha → 40-character lowercase hex; the canonical schema (
build-result.json) enforces^[a-f0-9]{40}$ - triggered_by → free-form actor string; convention is
<kind>:<id> - priority → AMQP integer priority; the broker honours it when the queue is declared with
x-max-priority - metadata → escape hatch for non-canonical fields; consumers must treat unknown keys as opaque
Q&A
Q: Is metadata validated by the broker or consumer?
A: Neither. It is map[string]interface{} with omitempty; consumers iterate keys they recognise and ignore the rest.
Q: What happens if commit_sha is shorter than 40 chars?
A: The wire format does not enforce it (it is a Go string), but the downstream build-result.json schema does (^[a-f0-9]{40}$); a short SHA fails when the build row is later validated against the contract.
Q: Does the producer mint build_id or does the caller?
A: The caller (REST handler) mints build_id before publishing, so the synchronous response can return the ID even if the publish later retries.
Examples
A GitHub webhook arrives, the engine assigns build_id, writes a pending build row, then calls Producer.PublishBuildJob. If RabbitMQ is unreachable for 4+ seconds (1+2+ext-backoff), the producer returns; the engine flips the row to failed with reason “queue unreachable” and surfaces it in the websocket hub stream.
neighbors on the map
- ProtocolMessage Envelope adding a new wire message type
- EventEnvelope Wire Wrapper publishing a new domain event proto
- CI Transition Event Schema vendoring kahn_emit.py into a CI producer