Skip to content

feat(zeebe): autogenerate Zeebe gRPC facade from upstream proto#764

Open
jwulf wants to merge 6 commits into
mainfrom
feat/grpc-proto-sync
Open

feat(zeebe): autogenerate Zeebe gRPC facade from upstream proto#764
jwulf wants to merge 6 commits into
mainfrom
feat/grpc-proto-sync

Conversation

@jwulf

@jwulf jwulf commented May 2, 2026

Copy link
Copy Markdown
Member

Implements an end-to-end pipeline that keeps the hand-written Zeebe gRPC facade in sync with the upstream gateway.proto from camunda/camunda.

Closes #760

What changed

Phase 0 — Proto fetch

scripts/fetch-zeebe-proto.mjs downloads gateway.proto from camunda/camunda at the ref pinned in .zeebe-proto-version (currently stable/8.9), records the resolved commit SHA in src/proto/.zeebe-proto-source.json, and refreshes src/proto/zeebe.proto. A --check mode is provided for CI.

Phase 1 — Drift detector

scripts/check-grpc-coverage.mjs parses the proto via protobufjs and reports gaps against the hand-written facade:

  • RPCs missing from the ZBGrpc interface or the public ZeebeGrpcClient facade
  • Top-level messages missing from interfaces-grpc-1.0.ts
  • Fields on existing messages missing from their TS mirrors

Each missing RPC is classified into one of four shape-classes (1–3 are mechanically derivable; Class 4 requires a hand-designed facade).

Phase 2 — Additive-surface emitter

scripts/emit-grpc-additions.mjs mechanically derives additive surfaces (new types, ZBGrpc method signatures, and facade methods) for Class 1–3 RPCs. Class 4 RPCs cause a hard failure (exit 3) so a maintainer is alerted. Supports --print, --apply, and --json modes.

Phase 3 — 8.9 backfill

One-time backfill to bring the facade in line with stable/8.9. Adds new wire types, enums, optional fields on existing types, and a new evaluateConditional() facade method.

BREAKING CHANGE: ZeebeGrpcClient.deleteResource() now returns Promise<DeleteResourceResponse> instead of Promise<Record<string, never>>. Callers that ignored the return value are unaffected.

Phase 4 — CI integration

  • ci.yml — new grpc-drift job on push/PR verifies proto freshness and zero drift
  • proto-sync.yml — weekly workflow (Monday 04:00 UTC) fetches latest proto, runs the emitter, and opens a PR automatically

Phase 5 — Documentation

MAINTAINER.md and AGENT.md updated with the proto-sync pipeline procedures and cross-references.

npm scripts

Script Purpose
npm run fetch:zeebe-proto Download upstream proto
npm run check:zeebe-proto CI: verify local proto matches pinned ref
npm run check:grpc-coverage Human-readable coverage report
npm run check:grpc-drift CI: exit non-zero on any gap
npm run emit:grpc-additions Print proposed additive code

jwulf added 6 commits April 23, 2026 14:05
Adds scripts/fetch-zeebe-proto.mjs which downloads the Zeebe
gateway.proto from camunda/camunda at the ref pinned in
.zeebe-proto-version (currently stable/8.9), records the resolved
commit SHA in src/proto/.zeebe-proto-source.json, and refreshes
src/proto/zeebe.proto.

Also adds a --check mode for CI to fail if the local copy drifts
from upstream.

This is phase 0 of the proto-sync pipeline outlined in #760.

Refs #760
Adds scripts/check-grpc-coverage.mjs which loads src/proto/zeebe.proto
via protobufjs and reports gaps against the hand-written facade in
src/zeebe/:

  - RPCs missing from the ZBGrpc interface
  - RPCs missing from the public ZeebeGrpcClient facade
  - Top-level messages missing from interfaces-grpc-1.0.ts
  - Fields on existing messages missing from their TS mirrors

Each missing RPC is classified into one of four shape-classes:
  - Class 1 — pure pass-through
  - Class 2 — operationReference normalisation only
  - Class 3 — variables stringification
  - Class 4 — bespoke shaping required (nested *Request, file-bytes,
              real oneofs, or per-element variables)

Class 4 RPCs always require a human-designed facade. Classes 1-3 are
mechanically derivable from the proto and can be auto-emitted in a
later phase.

Synthetic oneofs from proto3 'optional' fields are filtered out and
do not trigger Class 4. Empty response messages, deprecated messages,
and inline-parameter request shapes are excluded from the missing-
messages report.

Refs #760
One-time backfill to bring the hand-written Zeebe gRPC facade in line
with the upstream gateway.proto pinned at camunda/camunda@stable/8.9.

Changes:

- interfaces-grpc-1.0.ts:
    - New optional fields on existing wire types (ActivatedJob,
      ActivateJobsRequest, StreamActivatedJobsRequest, CompleteJobRequest,
      CreateProcessInstanceRequest/Response, CreateProcessInstanceWith-
      ResultResponse, EvaluateDecisionResponse, EvaluatedDecision,
      TopologyResponse, BrokerInfo, SetVariablesRequest,
      ModifyProcessInstanceRequest, BroadcastSignalResponse).
    - New top-level types: UserTaskProperties, JobResult,
      JobResultCorrections, JobResultActivateElement, StringList,
      ProcessInstanceCreationRuntimeInstruction,
      TerminateProcessInstanceInstruction, MoveInstruction,
      SetVariablesResponse, BatchOperationCreatedResult,
      DeleteResourceResponse, EvaluateConditionalRequest,
      ProcessInstanceReference, EvaluateConditionalResponse.
    - New enums: TenantFilter, JobKind, ListenerEventType,
      BatchOperationTypeEnum.

- interfaces-1.0.ts: extend ZBGrpc with evaluateConditionalSync; switch
  deleteResourceSync return type from Record<string, never> to
  DeleteResourceResponse.

- ZeebeGrpcClient.ts: add public evaluateConditional() (Class 3 facade)
  and update deleteResource() return type to DeleteResourceResponse.

The drift detector (scripts/check-grpc-coverage.mjs) reports
"OK — no gaps detected." after this commit.

BREAKING CHANGE: ZeebeGrpcClient.deleteResource() now returns
Promise<DeleteResourceResponse> instead of Promise<Record<string, never>>.
Existing callers that ignored the return value are unaffected; callers
that destructured the previously-empty object should now receive
{ resourceKey } and, when deleteHistory was requested for a process
definition, a batchOperation handle.

Refs #760
Adds scripts/emit-grpc-additions.mjs which mechanically derives the
additive parts of the Zeebe gRPC facade from src/proto/zeebe.proto.

For each gap reported by check-grpc-coverage:

  - new top-level wire-type interfaces are emitted into
    interfaces-grpc-1.0.ts (proto field comments preserved as JSDoc;
    int64-family typed as string to match the existing convention)
  - new ZBGrpc method signatures are inserted into the ZBGrpc
    interface in interfaces-1.0.ts
  - new public facade methods are inserted into ZeebeGrpcClient.ts
    using one of three templates:
      Class 1 — pure pass-through
      Class 2 — operationReference?.toString() normalisation
      Class 3 — losslessStringify(variables) + tenantId default
  - new fields on existing wire-type interfaces are printed for the
    maintainer to splice in by hand (precise placement inside an
    existing interface body is not safe to automate)

The script HARD FAILS (exit 3) if any new RPC requires bespoke (Class
4) shaping — nested *Request fields, file-bytes, real oneofs, or
per-element variables. Those facades must be hand-written.

Modes:
  --print    (default) emit proposed source to stdout
  --apply    write new interfaces, ZBGrpc signatures, and facade
             methods into the source files (additions are appended in
             a clearly-marked auto-emitted region)
  --json     emit the work list as JSON for downstream tooling

Wired into npm scripts:
  npm run check:grpc-coverage  # human report
  npm run check:grpc-drift     # CI: exit non-zero on any gap
  npm run emit:grpc-additions  # print proposed additive code

Verified end-to-end against the current 8.9 proto by removing
StringList, evaluateConditionalSync, and the evaluateConditional
facade method, then re-applying via --apply: the output compiled
cleanly under tsc and the drift detector reported zero gaps.

Refs #760
Two CI integrations for the Zeebe gRPC proto-sync pipeline:

1. ci.yml — new `grpc-drift` job runs on every push (non-main) and
   pull request. It verifies:
     a. src/proto/zeebe.proto matches the upstream ref pinned in
        .zeebe-proto-version (`npm run check:zeebe-proto`)
     b. the SDK's hand-written facade in src/zeebe/ covers every RPC,
        message, and field defined in the proto
        (`npm run check:grpc-drift`, exits non-zero on any gap)

   This blocks PRs that modify the proto without updating the facade,
   and PRs that update the facade against a stale proto.

2. .github/workflows/proto-sync.yml — new weekly workflow (Mondays
   04:00 UTC, plus workflow_dispatch). Fetches the latest upstream
   gateway.proto at the pinned ref, runs the auto-emitter to backfill
   Class 1-3 additive surfaces, verifies tsc + drift detector are
   clean, and opens a pull request via peter-evans/create-pull-request.

   The PR body includes a maintainer checklist for splicing in
   newly-reported optional fields on existing wire-types (which the
   emitter prints but does not auto-apply).

   If a new RPC requires bespoke (Class 4) shaping, the emitter exits
   with code 3 and the workflow fails — alerting a maintainer to
   design the facade by hand.

Refs #760
- MAINTAINER.md §7: new "Sync the Zeebe gateway.proto" subsection
  covering pipeline scripts, npm aliases, the bump-the-pinned-ref
  procedure, the weekly proto-sync.yml workflow, and the four
  request shape-classes used by the auto-emitter.
- MAINTAINER.md §2 (CI Workflows table): add a row for
  proto-sync.yml.
- MAINTAINER.md §8 (Cross-references): link the workflow, the
  pinned-ref file, and the three pipeline scripts.
- AGENT.md: short "Updating the Zeebe gRPC facade" subsection under
  Common Development Tasks pointing at the npm scripts and the
  MAINTAINER section.

Refs #760
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Autogeneration from Camunda gRPC protocol file

1 participant