Skip to content

Add a code generator for boilerplate to move data in and out of protobuf messages for grpc#1007

Open
tmckayus wants to merge 3 commits intoNVIDIA:release/26.04from
tmckayus:grpc-codegen
Open

Add a code generator for boilerplate to move data in and out of protobuf messages for grpc#1007
tmckayus wants to merge 3 commits intoNVIDIA:release/26.04from
tmckayus:grpc-codegen

Conversation

@tmckayus
Copy link
Copy Markdown
Contributor

This change adds a code generator written in Python which generates the protobuf specifications and the code to map between cuopt problem, solution, and settings structures and protobuf messages. The protoc compiler is still used to build the serialization/deserialziation code and the grpc interface -- only the high-level .proto files are generated from the yaml description.

The code generator reads a lightweight yaml file which describes the cuopt structures and generates mapping functions which are included in the grpc client and server implementations.

When a field is added/changed in the problem, solution, or settings structures, a developer updates the yaml file and re-runs the code generator, then reviews the updated code.

A README.md file in the codegen directory describes the yaml format, defaults, and conventions. As much as possible has been left to defaults or naming conventions to make the yaml easy to maintain.

@tmckayus tmckayus requested review from a team as code owners March 31, 2026 16:18
@tmckayus tmckayus added non-breaking Introduces a non-breaking change improvement Improves an existing functionality P1 labels Mar 31, 2026
@tmckayus tmckayus added this to the 26.04 milestone Mar 31, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a code generation pipeline for gRPC protobuf conversions, powered by a YAML field registry and Python generator. Data type definitions migrate from cuopt_remote.proto to a new generated cuopt_remote_data.proto. Mapper implementations refactor to use generated code via includes. The default gRPC port changes from 8765 to 5001. New host accessor methods are added for quadratic objective data.

Changes

Cohort / File(s) Summary
Code Generation Infrastructure
cpp/codegen/field_registry.yaml, cpp/codegen/generate_conversions.py, build.sh, cpp/CMakeLists.txt
Introduces YAML registry defining enum domains, solution/settings/problem schemas with field mappings, and Python generator (2446 lines) producing proto and C++ .inc files. Build script adds codegen target; CMake integrates codegen steps and includes generated proto path.
Proto Definition Migration
cpp/src/grpc/cuopt_remote.proto, cpp/src/grpc/cuopt_remote_service.proto
Removes 264 lines of type definitions (enums, messages) from cuopt_remote.proto, replaced by import public "cuopt_remote_data.proto". Adds problem_category field to ChunkedProblemHeader; removes ResultArrayDescriptor and ChunkedResultHeader definitions from service proto.
Mapper Implementation Refactoring
cpp/src/grpc/grpc_problem_mapper.cpp, cpp/src/grpc/grpc_settings_mapper.cpp, cpp/src/grpc/grpc_solution_mapper.cpp, cpp/src/grpc/grpc_solution_mapper.hpp, cpp/src/grpc/server/grpc_field_element_size.hpp
Replaces 595-line hand-written mappings with #include "generated_*.inc" directives. Removes enum conversion functions, chunked array logic, and explicit field population loops from mappers. Removes 28 public function declarations for termination status conversions from header.
API Additions
cpp/include/cuopt/linear_programming/cpu_optimization_problem.hpp
Adds three host getter accessors for quadratic objective data (get_quadratic_objective_offsets_host(), get_quadratic_objective_indices_host(), get_quadratic_objective_values_host()), delegating to existing non-_host methods.
Default Port & Endpoint Updates
cpp/src/grpc/server/grpc_server_main.cpp, cpp/src/grpc/server/grpc_server_types.hpp, cpp/src/grpc/client/grpc_client.hpp, GRPC_QUICK_START.md
Changes default gRPC port from 8765 to 5001 across server config, CLI defaults, and client endpoint examples. Updates documentation port references.
Test Updates
cpp/tests/linear_programming/grpc/grpc_client_test.cpp, cpp/tests/linear_programming/grpc/grpc_pipe_serialization_test.cpp, cpp/tests/linear_programming/grpc/CMakeLists.txt
Updates mocked solution construction (e.g., set_lp_termination_status vs set_termination_status), replaces is_mip() boolean with problem_category() enum. Adds new SettingsMapperTest round-trip tests. Updates include paths for generated code.
Dependencies & Environment
conda/environments/all_cuda-*, conda/recipes/libcuopt/recipe.yaml, dependencies.yaml
Adds pyyaml and ruamel.yaml>=0.18 to conda environment and build dependencies across multiple architecture/CUDA variants.
CI & Verification
ci/verify_codegen.sh, ci/test_cpp.sh
Adds new verification script that regenerates output and diffs against committed files; integrates verification step into CI pipeline.
Documentation
docs/cuopt/grpc/GRPC_CODE_GENERATION.md, docs/cuopt/grpc/GRPC_INTERFACE.md, GRPC_SERVER_ARCHITECTURE.md, GRPC_INTERFACE.md (removed)
Adds 712-line codegen schema/usage documentation and 133-line chunked transfer protocol spec. Removes 392-line GRPC_INTERFACE.md from root. Updates server architecture diagrams and port references.
Miscellaneous
cpp/src/grpc/client/grpc_client.cpp
Updates debug log field from header->is_mip() to header->problem_category().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Main merge release/26.04 1 #1005: Overlapping changes to gRPC codegen infrastructure, protobuf definitions, CMake integration, and mapper refactoring with generated code includes across the same core files.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a code generator for boilerplate to move data in and out of protobuf messages for gRPC.
Description check ✅ Passed The description is well-related to the changeset, explaining the Python code generator, YAML configuration, mapping functions, and the workflow for updating structures.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (11)
build.sh (1)

361-368: Make regeneration incremental or explicit.

This now runs the generator on every libcuopt / cuopt_grpc_server build, which makes a normal compile rewrite repo-local generated sources and adds a hard Python/codegen dependency even when nothing changed. Given the workflow in the PR description, this looks better as an explicit regen target or a timestamp-based check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build.sh` around lines 361 - 368, The regeneration block unconditionally runs
the Python generator when building libcuopt/cuopt_grpc_server; change it to
either an explicit make target (e.g., add a "regen-codegen" target) or guard the
python invocation with a timestamp/hash check against field_registry.yaml and
generated outputs: keep the current conditional on buildAll/hasArg but require
an explicit arg (like hasArg regen_codegen) or compare modification times
(field_registry.yaml vs files in cpp/codegen/generated) and only invoke python
generate_conversions.py when the registry is newer or the generated files are
missing; update buildAll/hasArg usage and the python call site in this block to
implement the chosen approach.
cpp/codegen/generated_baseline/generated_build_array_chunks.inc (1)

46-60: Consider explicit handling of all var_t enum values in the generator.

The default: vt_bytes.push_back('C'); case (line 55) silently treats unknown variable types as CONTINUOUS. If additional var_t enum values are added in the future (e.g., BINARY, SEMI_CONTINUOUS), this could silently produce incorrect behavior.

Since this is auto-generated code, consider updating the generator (generate_conversions.py) to either:

  1. Enumerate all known var_t values explicitly
  2. Throw an exception for unknown types instead of defaulting

This is a minor defensive coding suggestion for the generator—the current code works correctly for the existing enum values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generated_baseline/generated_build_array_chunks.inc` around lines
46 - 60, The generator currently emits a switch over var_t that uses a default
mapping to 'C', which can silently mis-handle new enum values; update
generate_conversions.py so the generated code explicitly handles every var_t
enum member (e.g., CONTINUOUS, INTEGER, BINARY, SEMI_CONTINUOUS, etc.) in the
switch that builds vt_bytes (the block around
problem.get_variable_types_host()), and if an unknown enum value is encountered
emit code that throws or logs an error instead of using a permissive default;
ensure the generated switch produces a clear failure path (exception or assert)
referencing var_t so chunk_byte_blob(requests,
cuopt::remote::FIELD_VARIABLE_TYPES, vt_bytes, upload_id, chunk_size_bytes) only
receives validated values.
cpp/codegen/generated_baseline/generated_chunked_arrays_to_problem.inc (1)

120-120: Variable shadowing: loop variable c shadows objective coefficients c.

The loop variable char c on line 120 shadows the auto c variable declared on line 81 for objective coefficients. While this is functionally safe (the outer c is no longer used), it's poor style that could cause confusion during debugging.

Since this is auto-generated code, consider updating the generator to use a different loop variable name (e.g., ch or vt_char).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generated_baseline/generated_chunked_arrays_to_problem.inc` at
line 120, The loop variable "char c" inside the for loop over var_types_str
shadows the outer objective coefficient variable named "c"; rename the loop
variable (e.g., to "ch" or "vt_char") in the generated code so it no longer
collides with the outer "c" used for objective coefficients (update the
generator that emits the for (char c : var_types_str) to use the new name and
ensure any uses inside the loop reference that new identifier).
cpp/codegen/README.md (1)

26-72: Add language specifier to fenced code block.

The file layout code block lacks a language specifier, which triggers a markdownlint warning. Use text or plaintext for ASCII tree diagrams.

📝 Suggested fix
-```
+```text
 cpp/codegen/
 ├── field_registry.yaml          # Source of truth for all fields
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/README.md` around lines 26 - 72, The fenced code block that shows
the ASCII tree starting with "cpp/codegen/" in README.md is missing a language
specifier; update the opening fence from ``` to ```text (or ```plaintext) so the
block is recognized as plain text and the markdownlint warning is resolved.
cpp/codegen/generated_baseline/cuopt_remote_data.proto (2)

6-6: Package directory mismatch flagged by static analysis.

The proto package cuopt.remote conventionally expects the file to reside in a cuopt/remote/ directory structure, but this generated file is placed in cpp/codegen/generated_baseline/. While this may work with the current CMake protobuf generation setup, it could cause issues with other proto tooling (e.g., Buf, language-specific proto plugins that rely on directory conventions).

Since this is auto-generated, consider updating the generator or build configuration to either:

  1. Place generated protos in a directory matching the package path, or
  2. Configure proto tooling to ignore this mismatch if intentional
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generated_baseline/cuopt_remote_data.proto` at line 6, The proto
package declaration "package cuopt.remote;" in cuopt_remote_data.proto does not
match the file's generated location; update the generator or build config so
generated protos are emitted under a directory that matches the package path
(e.g., cuopt/remote/) or, if intentional, configure the proto tooling to
ignore/package-mismatch (e.g., adjust generator output path or your proto
toolchain settings) so tools like Buf and language-specific plugins won't fail
on the package-directory mismatch.

30-36: Enum values lack prefixes unlike other enums.

The PDLPSolverMode enum values (Stable1, Stable2, etc.) don't have a prefix, while PDLPTerminationStatus and MIPTerminationStatus use prefixes (PDLP_, MIP_). Similarly, LPMethod values are unprefixed. This inconsistency is minor but could cause confusion or naming collisions in some language bindings.

Consider adding a PDLP_MODE_ or similar prefix to solver mode values for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generated_baseline/cuopt_remote_data.proto` around lines 30 - 36,
The enum PDLPSolverMode defines values Stable1, Stable2, Methodical1, Fast1,
Stable3 without the project prefix; update these identifiers to use a consistent
prefix (e.g., PDLP_MODE_) such as PDLP_MODE_STABLE1, PDLP_MODE_STABLE2,
PDLP_MODE_METHODICAL1, PDLP_MODE_FAST1, PDLP_MODE_STABLE3 in the enum
PDLPSolverMode declaration and then update all references/usages of
PDLPSolverMode values throughout the codebase (serialization/deserialization
sites, switch/case, tests, and any generated bindings) to the new names to keep
naming consistent with PDLP/MIP termination enums.
cpp/codegen/generate_conversions.py (4)

1297-1335: Unused loop variable gname in setter_groups iteration.

The loop variable gname is not used within the loop body. This is a minor code smell flagged by Ruff (B007).

Proposed fix
-    for gname, gdef in setter_groups.items():
+    for _gname, gdef in setter_groups.items():
         cond = gdef.get("to_proto_condition")

Apply similar changes at lines 1374, 1783, and 2022.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 1297 - 1335, The for-loop
over setter_groups currently uses an unused variable gname; change the loop
header to use a throwaway variable (for example: for _, gdef in
setter_groups.items():) to silence the Ruff B007 warning and make intent clear;
apply the same replacement in the other occurrences mentioned (the loops around
the code that references _find_problem_field, _proto_cpp_name,
_default_problem_getter, and _array_element_cast at the comparable blocks near
the later occurrences).

2180-2193: Convert lambda assignments to function definitions.

Ruff flags E731 for lambda assignments. While functional, using def is more idiomatic Python and provides better stack traces for debugging.

Proposed fix
 def _normalize_bare_strings(entries):
     """Convert bare string entries in a field list to single-key dicts.
     This lets the auto-numbering logic treat them uniformly."""
     try:
         from ruamel.yaml.comments import CommentedMap

         mk_inner = CommentedMap
-        mk_outer = lambda name, inner: CommentedMap([(name, inner)])
+        def mk_outer(name, inner):
+            return CommentedMap([(name, inner)])
     except ImportError:
         mk_inner = dict
-        mk_outer = lambda name, inner: {name: inner}
+        def mk_outer(name, inner):
+            return {name: inner}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 2180 - 2193, The mk_outer
lambda should be replaced with a proper function definition to avoid E731;
locate the mk_outer assignment in the try and except branches and replace each
"mk_outer = lambda name, inner: ..." with a named def mk_outer(name, inner):
that returns the corresponding CommentedMap([(name, inner)]) in the try branch
and {name: inner} in the except branch, leaving mk_inner and subsequent logic
(the loop over entries and handling of entries.ca) unchanged.

41-44: Specify explicit encoding for file operations.

The write_file function opens files without specifying encoding. While Python 3 defaults to UTF-8 on most systems, explicitly specifying encoding="utf-8" ensures consistent behavior across platforms and avoids potential issues on Windows where the default encoding may differ.

Proposed fix
 def write_file(path, content):
     os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
-    with open(path, "w") as f:
+    with open(path, "w", encoding="utf-8") as f:
         f.write(content)
     print(f"  wrote {path}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 41 - 44, The write_file
helper currently opens files without an explicit encoding; update the
write_file(path, content) function to open the file with encoding="utf-8" (i.e.,
pass encoding to open) so writes are deterministic across platforms and avoid
Windows default-encoding issues; keep os.makedirs(os.path.dirname(path) or ".",
exist_ok=True) and the function signature unchanged.

2409-2421: Add explicit encoding when reading/writing YAML files.

File operations at lines 2409, 2414, and 2420 lack explicit encoding. This can cause issues on systems with non-UTF-8 default encodings.

Proposed fix
-        with open(args.registry) as f:
+        with open(args.registry, encoding="utf-8") as f:
             rt_data = ryaml.load(f)

         n = auto_assign_field_numbers(rt_data)
         if n > 0:
-            with open(args.registry, "w") as f:
+            with open(args.registry, "w", encoding="utf-8") as f:
                 ryaml.dump(rt_data, f)
             print(f"  auto-numbered {n} field(s) in {args.registry}")
         else:
             print("  all field numbers already assigned")

-    with open(args.registry) as f:
+    with open(args.registry, encoding="utf-8") as f:
         registry = yaml.safe_load(f)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 2409 - 2421, The three file
opens that read/write YAML (the open calls around ryaml.load, ryaml.dump, and
yaml.safe_load that use args.registry) lack explicit encoding; update each
open(...) to specify encoding="utf-8" so reads/writes are deterministic across
platforms (the locations to change are the open used with ryaml.load, the open
used with ryaml.dump, and the final open used with yaml.safe_load).
cpp/src/grpc/cuopt_remote_data.proto (1)

1-7: Package directory mismatch flagged by Buf linter.

The Buf linter reports that files with package cuopt.remote should be in directory cuopt/remote, but this file is in cpp/src/grpc. This is a common project structure choice where proto files are co-located with their consumers rather than following a package-based directory layout. If this is intentional, consider adding a buf.yaml exception or adjusting Buf configuration to suppress this warning.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/src/grpc/cuopt_remote_data.proto` around lines 1 - 7, The Buf linter
flags that the proto package "cuopt.remote" (in cuopt_remote_data.proto) does
not match the file directory (currently cpp/src/grpc); either move
cuopt_remote_data.proto into a directory matching the package (cuopt/remote) or
add a Buf configuration exception: update buf.yaml to allow files located under
cpp/src/grpc for package cuopt.remote (e.g., configure module/file/ignore or add
an allowlist) so the linter stops reporting the package-directory mismatch; pick
whichever approach (relocate the file or adjust buf.yaml) fits project
conventions and apply it consistently for other generated proto files as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cpp/codegen/generated_baseline/generated_proto_to_pdlp_settings.inc`:
- Line 22: The enum casts like static_cast<presolver_t>(pb_settings.presolver())
and the similar static_cast<pdlp_precision_t> must be guarded by a validation
step in the generator: update the generator that emits
generated_proto_to_pdlp_settings.inc to read the C++ enum macro values from
constants.h and compare them against the proto integer values from
field_registry.yaml (the pb_settings.<field>() values) before emitting raw
static_casts; if any mismatch is detected, emit a runtime-check or generate code
that logs/throws a clear error (including the enum name, macro value, and proto
value) and fail the build/generation, ensuring the validation covers presolver_t
and pdlp_precision_t and any other enums converted this way.

In `@cpp/codegen/generated_baseline/generated_proto_to_problem.inc`:
- Around line 64-72: The loop mapping vt_str -> vtypes must not treat unknown
codes as CONTINUOUS; update the switch in the iterator over vt_str so that the
default branch reports/propagates a validation error (e.g., throw
std::invalid_argument or return a validation result) including the offending
char, instead of pushing var_t::CONTINUOUS; ensure has_integers and vtypes are
only updated for recognized codes (cases 'C','I','B') and that the caller of
this generated_proto_to_problem.inc surface the validation failure rather than
proceeding to solve.

In `@cpp/codegen/generated_baseline/generated_result_header_fields.proto.inc`:
- Around line 1-35: The generated baseline file has stale field numbers (e.g.,
PDLPTerminationStatus lp_termination_status, MIPTerminationStatus
mip_termination_status and many scalar fields) that do not match the current
proto (cuopt_remote_data.proto uses 1000/2000 ranges); fix by regenerating the
baseline with the codegen script (generate_conversions.py) so
generated_result_header_fields.proto.inc reflects the current proto field
numbers, or alternatively add clear documentation near the generator invocation
explaining why this file is intentionally diverged and how to update it (mention
lp_termination_status, mip_termination_status, and generate_conversions.py to
locate the relevant code paths).

---

Nitpick comments:
In `@build.sh`:
- Around line 361-368: The regeneration block unconditionally runs the Python
generator when building libcuopt/cuopt_grpc_server; change it to either an
explicit make target (e.g., add a "regen-codegen" target) or guard the python
invocation with a timestamp/hash check against field_registry.yaml and generated
outputs: keep the current conditional on buildAll/hasArg but require an explicit
arg (like hasArg regen_codegen) or compare modification times
(field_registry.yaml vs files in cpp/codegen/generated) and only invoke python
generate_conversions.py when the registry is newer or the generated files are
missing; update buildAll/hasArg usage and the python call site in this block to
implement the chosen approach.

In `@cpp/codegen/generate_conversions.py`:
- Around line 1297-1335: The for-loop over setter_groups currently uses an
unused variable gname; change the loop header to use a throwaway variable (for
example: for _, gdef in setter_groups.items():) to silence the Ruff B007 warning
and make intent clear; apply the same replacement in the other occurrences
mentioned (the loops around the code that references _find_problem_field,
_proto_cpp_name, _default_problem_getter, and _array_element_cast at the
comparable blocks near the later occurrences).
- Around line 2180-2193: The mk_outer lambda should be replaced with a proper
function definition to avoid E731; locate the mk_outer assignment in the try and
except branches and replace each "mk_outer = lambda name, inner: ..." with a
named def mk_outer(name, inner): that returns the corresponding
CommentedMap([(name, inner)]) in the try branch and {name: inner} in the except
branch, leaving mk_inner and subsequent logic (the loop over entries and
handling of entries.ca) unchanged.
- Around line 41-44: The write_file helper currently opens files without an
explicit encoding; update the write_file(path, content) function to open the
file with encoding="utf-8" (i.e., pass encoding to open) so writes are
deterministic across platforms and avoid Windows default-encoding issues; keep
os.makedirs(os.path.dirname(path) or ".", exist_ok=True) and the function
signature unchanged.
- Around line 2409-2421: The three file opens that read/write YAML (the open
calls around ryaml.load, ryaml.dump, and yaml.safe_load that use args.registry)
lack explicit encoding; update each open(...) to specify encoding="utf-8" so
reads/writes are deterministic across platforms (the locations to change are the
open used with ryaml.load, the open used with ryaml.dump, and the final open
used with yaml.safe_load).

In `@cpp/codegen/generated_baseline/cuopt_remote_data.proto`:
- Line 6: The proto package declaration "package cuopt.remote;" in
cuopt_remote_data.proto does not match the file's generated location; update the
generator or build config so generated protos are emitted under a directory that
matches the package path (e.g., cuopt/remote/) or, if intentional, configure the
proto tooling to ignore/package-mismatch (e.g., adjust generator output path or
your proto toolchain settings) so tools like Buf and language-specific plugins
won't fail on the package-directory mismatch.
- Around line 30-36: The enum PDLPSolverMode defines values Stable1, Stable2,
Methodical1, Fast1, Stable3 without the project prefix; update these identifiers
to use a consistent prefix (e.g., PDLP_MODE_) such as PDLP_MODE_STABLE1,
PDLP_MODE_STABLE2, PDLP_MODE_METHODICAL1, PDLP_MODE_FAST1, PDLP_MODE_STABLE3 in
the enum PDLPSolverMode declaration and then update all references/usages of
PDLPSolverMode values throughout the codebase (serialization/deserialization
sites, switch/case, tests, and any generated bindings) to the new names to keep
naming consistent with PDLP/MIP termination enums.

In `@cpp/codegen/generated_baseline/generated_build_array_chunks.inc`:
- Around line 46-60: The generator currently emits a switch over var_t that uses
a default mapping to 'C', which can silently mis-handle new enum values; update
generate_conversions.py so the generated code explicitly handles every var_t
enum member (e.g., CONTINUOUS, INTEGER, BINARY, SEMI_CONTINUOUS, etc.) in the
switch that builds vt_bytes (the block around
problem.get_variable_types_host()), and if an unknown enum value is encountered
emit code that throws or logs an error instead of using a permissive default;
ensure the generated switch produces a clear failure path (exception or assert)
referencing var_t so chunk_byte_blob(requests,
cuopt::remote::FIELD_VARIABLE_TYPES, vt_bytes, upload_id, chunk_size_bytes) only
receives validated values.

In `@cpp/codegen/generated_baseline/generated_chunked_arrays_to_problem.inc`:
- Line 120: The loop variable "char c" inside the for loop over var_types_str
shadows the outer objective coefficient variable named "c"; rename the loop
variable (e.g., to "ch" or "vt_char") in the generated code so it no longer
collides with the outer "c" used for objective coefficients (update the
generator that emits the for (char c : var_types_str) to use the new name and
ensure any uses inside the loop reference that new identifier).

In `@cpp/codegen/README.md`:
- Around line 26-72: The fenced code block that shows the ASCII tree starting
with "cpp/codegen/" in README.md is missing a language specifier; update the
opening fence from ``` to ```text (or ```plaintext) so the block is recognized
as plain text and the markdownlint warning is resolved.

In `@cpp/src/grpc/cuopt_remote_data.proto`:
- Around line 1-7: The Buf linter flags that the proto package "cuopt.remote"
(in cuopt_remote_data.proto) does not match the file directory (currently
cpp/src/grpc); either move cuopt_remote_data.proto into a directory matching the
package (cuopt/remote) or add a Buf configuration exception: update buf.yaml to
allow files located under cpp/src/grpc for package cuopt.remote (e.g., configure
module/file/ignore or add an allowlist) so the linter stops reporting the
package-directory mismatch; pick whichever approach (relocate the file or adjust
buf.yaml) fits project conventions and apply it consistently for other generated
proto files as well.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4d031009-1820-4b8d-86c4-e9139d68680d

📥 Commits

Reviewing files that changed from the base of the PR and between 9f50244 and 0fc3c03.

⛔ Files ignored due to path filters (30)
  • cpp/codegen/generated/cuopt_remote_data.proto is excluded by !**/generated/**
  • cpp/codegen/generated/generated_array_field_element_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_build_array_chunks.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_arrays_to_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_header_to_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_to_lp_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_to_mip_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_collect_lp_arrays.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_collect_mip_arrays.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_enum_converters_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_enum_converters_settings.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_enum_converters_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_estimate_lp_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_estimate_mip_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_estimate_problem_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_lp_chunked_header.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_lp_solution_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_mip_chunked_header.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_mip_settings_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_mip_solution_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_pdlp_settings_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_populate_chunked_header_lp.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_populate_chunked_header_mip.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_problem_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_lp_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_mip_settings.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_mip_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_pdlp_settings.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_result_enums.proto.inc is excluded by !**/generated/**
📒 Files selected for processing (45)
  • GRPC_SERVER_ARCHITECTURE.md
  • build.sh
  • cpp/CMakeLists.txt
  • cpp/codegen/README.md
  • cpp/codegen/field_registry.yaml
  • cpp/codegen/generate_conversions.py
  • cpp/codegen/generated_baseline/cuopt_remote_data.proto
  • cpp/codegen/generated_baseline/generated_array_field_element_size.inc
  • cpp/codegen/generated_baseline/generated_build_array_chunks.inc
  • cpp/codegen/generated_baseline/generated_chunked_arrays_to_problem.inc
  • cpp/codegen/generated_baseline/generated_chunked_header_to_problem.inc
  • cpp/codegen/generated_baseline/generated_chunked_to_lp_solution.inc
  • cpp/codegen/generated_baseline/generated_chunked_to_mip_solution.inc
  • cpp/codegen/generated_baseline/generated_collect_lp_arrays.inc
  • cpp/codegen/generated_baseline/generated_collect_mip_arrays.inc
  • cpp/codegen/generated_baseline/generated_enum_converters.inc
  • cpp/codegen/generated_baseline/generated_estimate_lp_size.inc
  • cpp/codegen/generated_baseline/generated_estimate_mip_size.inc
  • cpp/codegen/generated_baseline/generated_estimate_problem_size.inc
  • cpp/codegen/generated_baseline/generated_lp_chunked_header.inc
  • cpp/codegen/generated_baseline/generated_lp_solution_to_proto.inc
  • cpp/codegen/generated_baseline/generated_mip_chunked_header.inc
  • cpp/codegen/generated_baseline/generated_mip_settings_to_proto.inc
  • cpp/codegen/generated_baseline/generated_mip_solution_to_proto.inc
  • cpp/codegen/generated_baseline/generated_pdlp_settings_to_proto.inc
  • cpp/codegen/generated_baseline/generated_populate_chunked_header_lp.inc
  • cpp/codegen/generated_baseline/generated_populate_chunked_header_mip.inc
  • cpp/codegen/generated_baseline/generated_problem_to_proto.inc
  • cpp/codegen/generated_baseline/generated_proto_to_lp_solution.inc
  • cpp/codegen/generated_baseline/generated_proto_to_mip_settings.inc
  • cpp/codegen/generated_baseline/generated_proto_to_mip_solution.inc
  • cpp/codegen/generated_baseline/generated_proto_to_pdlp_settings.inc
  • cpp/codegen/generated_baseline/generated_proto_to_problem.inc
  • cpp/codegen/generated_baseline/generated_result_enums.proto.inc
  • cpp/codegen/generated_baseline/generated_result_header_fields.proto.inc
  • cpp/include/cuopt/linear_programming/cpu_optimization_problem.hpp
  • cpp/src/grpc/client/grpc_client.cpp
  • cpp/src/grpc/cuopt_remote.proto
  • cpp/src/grpc/cuopt_remote_data.proto
  • cpp/src/grpc/cuopt_remote_service.proto
  • cpp/src/grpc/grpc_problem_mapper.cpp
  • cpp/src/grpc/grpc_settings_mapper.cpp
  • cpp/src/grpc/grpc_solution_mapper.cpp
  • cpp/src/grpc/grpc_solution_mapper.hpp
  • cpp/src/grpc/server/grpc_field_element_size.hpp
💤 Files with no reviewable changes (1)
  • cpp/src/grpc/grpc_solution_mapper.hpp

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
cpp/src/grpc/grpc_solution_mapper.cpp (1)

35-42: Helper function doubles_to_bytes always converts to double regardless of f_t.

The template function converts vec elements to double before serialization. When f_t is float, this performs an implicit widening conversion. While this ensures wire-format consistency, it may be intentional to always transmit as double for precision. Verify this matches the expected behavior and proto schema (which uses repeated double for numeric arrays).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/src/grpc/grpc_solution_mapper.cpp` around lines 35 - 42, The helper
doubles_to_bytes currently converts the input vector elements to double
unconditionally inside doubles_to_bytes, which widens floats implicitly; decide
whether the wire format must be repeated double or should preserve the original
f_t size. If the proto expects repeated double, make the conversion explicit and
document/rename the function to reflect that it serializes as doubles (and
ensure no loss of intent); otherwise change the implementation to serialize the
original type by using a temporary vector of f_t (or directly memcpy vec.data())
and use sizeof(f_t) so floats remain 32-bit on the wire. Update doubles_to_bytes
and any callers accordingly and add a static_assert or comment referencing the
proto schema (repeated double) to make the choice explicit.
cpp/src/grpc/grpc_settings_mapper.cpp (2)

14-16: Potentially unused includes after refactor.

The <stdexcept> and <string> headers may no longer be needed since the hand-written enum conversion logic (which used std::invalid_argument) has been replaced with generated code. Consider removing them if the generated .inc files don't require them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/src/grpc/grpc_settings_mapper.cpp` around lines 14 - 16, The includes
<stdexcept> and <string> in grpc_settings_mapper.cpp appear to be vestigial
after switching to generated enum conversion code; remove those two lines and
rebuild to confirm they aren't required by the included generated .inc files
(leave <limits> if still used); if the build breaks, reintroduce only the
specific header (stdexcept or string) required by the failing translation unit
or update the generated .inc to include the needed header so the top-level file
remains minimal.

20-22: Enum conversion now silently defaults unknown values instead of throwing.

The generated enum converter (per context snippet cpp/codegen/generated/generated_enum_converters_settings.inc) returns a default value (e.g., Stable3) for unknown enum values rather than throwing std::invalid_argument. This is a behavioral change from the previous hand-written mappers. This is actually correct for proto3, which requires graceful handling of unknown enum values, but callers that previously relied on exceptions for invalid enum detection will need adjustment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/src/grpc/grpc_settings_mapper.cpp` around lines 20 - 22, The generated
enum converters included via generated_enum_converters_settings.inc now map
unknown proto enum values to a default (e.g., Stable3) instead of throwing
std::invalid_argument, so update callers or add a thin validation wrapper in
grpc_settings_mapper.cpp: implement a function (e.g., ValidateAndMapSettingEnum)
that calls the generated converter from generated_enum_converters_settings.inc,
detects the sentinel/default result (or an unrecognized proto ordinal) and
throws std::invalid_argument for unknown values, and then replace direct calls
to the generated converter with this wrapper (or else update each caller to
handle the default result explicitly).
cpp/codegen/generated_baseline/cuopt_remote_data.proto (1)

6-6: Buf lint error: Package directory mismatch.

The static analysis reports that files with package cuopt.remote should be in a cuopt/remote directory, but this file is in cpp/codegen/generated_baseline. This is likely acceptable for a baseline/reference file that isn't directly compiled by protoc, but consider adding this path to buf's exclude list or documenting that this is intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generated_baseline/cuopt_remote_data.proto` at line 6, The proto
package declaration "package cuopt.remote" in cuopt_remote_data.proto conflicts
with buf's package-directory rule; either move or duplicate this file under a
matching directory structure (cuopt/remote) or add the current path
(cpp/codegen/generated_baseline) to buf's exclude/ignore configuration so buf
lint accepts this baseline; update the project's buf.yaml (or documentation) to
reflect the intentional exception and reference the proto by its package name
"cuopt.remote" so reviewers can find the impacted file.
cpp/codegen/generate_conversions.py (2)

1-5: Duplicate SPDX license identifier.

Line 5 duplicates the license identifier already on line 3.

📝 Suggested fix
 #!/usr/bin/env python3
 # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES.
 # SPDX-License-Identifier: Apache-2.0
 # All rights reserved.
-# SPDX-License-Identifier: Apache-2.0
 """
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 1 - 5, The file header
contains a duplicated SPDX license identifier; remove the redundant
"SPDX-License-Identifier: Apache-2.0" entry so only a single
SPDX-License-Identifier line remains in cpp/codegen/generate_conversions.py
(keep the canonical SPDX header and delete the duplicate on line 5).

1822-1862: Generator produces silent default for unknown variable types.

The _gen_chunked_arrays_to_problem function (and similarly _gen_proto_to_problem around lines 1423-1442) generates code that silently maps unrecognized variable type characters to var_t::CONTINUOUS. This was flagged in past reviews on the generated .inc files.

Consider updating the generator to emit validation logic that throws or logs an error for unknown codes instead of silently defaulting. For example, the generated switch could include a default case that reports the offending character.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 1822 - 1862, The generator
currently emits switch logic that maps unknown variable type characters silently
to var_t::CONTINUOUS; update _gen_chunked_arrays_to_problem (and similarly
_gen_proto_to_problem) so the generated switch includes a default case that
reports the offending character and fails fast (e.g., throw std::runtime_error
or call an error logger) instead of defaulting. In practice, change the code
that emits the switch for variable types (look for the logic emitting
var_t::CONTINUOUS) to append a default: branch that includes the field/variable
identifier and the unexpected character in the error message so the compiled
.inc will surface invalid codes at runtime or during initialization. Ensure the
emitted message references the variable name/field and the raw char so debugging
is straightforward.
cpp/codegen/README.md (1)

26-26: Add language specifier to fenced code block.

The file layout code block is missing a language specifier. Using text or plaintext would satisfy the linter and improve syntax highlighting in some renderers.

📝 Suggested fix
-```
+```text
 cpp/codegen/
 ├── field_registry.yaml          # Source of truth for all fields
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/README.md` at line 26, The fenced code block that starts with the
snippet "cpp/codegen/" is missing a language specifier; update the opening
triple backticks for that block to include a language such as text or plaintext
(e.g., change ``` to ```text) so the linter passes and renderers get proper
highlighting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cpp/codegen/generated_baseline/cuopt_remote_data.proto`:
- Around line 218-219: The baseline proto is stale: update the generated file so
ChunkedResultHeader matches the current schema (replace the field `bool is_mip =
1;` with `ProblemCategory problem_category = 1;` as in
cpp/src/grpc/cuopt_remote_data.proto) by re-running the codegen/regeneration
process (invoke the project’s proto/codegen script used to produce
generated_baseline files) so the generated message ChunkedResultHeader and its
field definition align with the actual proto.

---

Nitpick comments:
In `@cpp/codegen/generate_conversions.py`:
- Around line 1-5: The file header contains a duplicated SPDX license
identifier; remove the redundant "SPDX-License-Identifier: Apache-2.0" entry so
only a single SPDX-License-Identifier line remains in
cpp/codegen/generate_conversions.py (keep the canonical SPDX header and delete
the duplicate on line 5).
- Around line 1822-1862: The generator currently emits switch logic that maps
unknown variable type characters silently to var_t::CONTINUOUS; update
_gen_chunked_arrays_to_problem (and similarly _gen_proto_to_problem) so the
generated switch includes a default case that reports the offending character
and fails fast (e.g., throw std::runtime_error or call an error logger) instead
of defaulting. In practice, change the code that emits the switch for variable
types (look for the logic emitting var_t::CONTINUOUS) to append a default:
branch that includes the field/variable identifier and the unexpected character
in the error message so the compiled .inc will surface invalid codes at runtime
or during initialization. Ensure the emitted message references the variable
name/field and the raw char so debugging is straightforward.

In `@cpp/codegen/generated_baseline/cuopt_remote_data.proto`:
- Line 6: The proto package declaration "package cuopt.remote" in
cuopt_remote_data.proto conflicts with buf's package-directory rule; either move
or duplicate this file under a matching directory structure (cuopt/remote) or
add the current path (cpp/codegen/generated_baseline) to buf's exclude/ignore
configuration so buf lint accepts this baseline; update the project's buf.yaml
(or documentation) to reflect the intentional exception and reference the proto
by its package name "cuopt.remote" so reviewers can find the impacted file.

In `@cpp/codegen/README.md`:
- Line 26: The fenced code block that starts with the snippet "cpp/codegen/" is
missing a language specifier; update the opening triple backticks for that block
to include a language such as text or plaintext (e.g., change ``` to ```text) so
the linter passes and renderers get proper highlighting.

In `@cpp/src/grpc/grpc_settings_mapper.cpp`:
- Around line 14-16: The includes <stdexcept> and <string> in
grpc_settings_mapper.cpp appear to be vestigial after switching to generated
enum conversion code; remove those two lines and rebuild to confirm they aren't
required by the included generated .inc files (leave <limits> if still used); if
the build breaks, reintroduce only the specific header (stdexcept or string)
required by the failing translation unit or update the generated .inc to include
the needed header so the top-level file remains minimal.
- Around line 20-22: The generated enum converters included via
generated_enum_converters_settings.inc now map unknown proto enum values to a
default (e.g., Stable3) instead of throwing std::invalid_argument, so update
callers or add a thin validation wrapper in grpc_settings_mapper.cpp: implement
a function (e.g., ValidateAndMapSettingEnum) that calls the generated converter
from generated_enum_converters_settings.inc, detects the sentinel/default result
(or an unrecognized proto ordinal) and throws std::invalid_argument for unknown
values, and then replace direct calls to the generated converter with this
wrapper (or else update each caller to handle the default result explicitly).

In `@cpp/src/grpc/grpc_solution_mapper.cpp`:
- Around line 35-42: The helper doubles_to_bytes currently converts the input
vector elements to double unconditionally inside doubles_to_bytes, which widens
floats implicitly; decide whether the wire format must be repeated double or
should preserve the original f_t size. If the proto expects repeated double,
make the conversion explicit and document/rename the function to reflect that it
serializes as doubles (and ensure no loss of intent); otherwise change the
implementation to serialize the original type by using a temporary vector of f_t
(or directly memcpy vec.data()) and use sizeof(f_t) so floats remain 32-bit on
the wire. Update doubles_to_bytes and any callers accordingly and add a
static_assert or comment referencing the proto schema (repeated double) to make
the choice explicit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f43cf9d9-dbe4-4c8d-bb72-b255a829bb29

📥 Commits

Reviewing files that changed from the base of the PR and between 0fc3c03 and 42ad23f.

⛔ Files ignored due to path filters (30)
  • cpp/codegen/generated/cuopt_remote_data.proto is excluded by !**/generated/**
  • cpp/codegen/generated/generated_array_field_element_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_build_array_chunks.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_arrays_to_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_header_to_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_to_lp_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_to_mip_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_collect_lp_arrays.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_collect_mip_arrays.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_enum_converters_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_enum_converters_settings.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_enum_converters_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_estimate_lp_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_estimate_mip_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_estimate_problem_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_lp_chunked_header.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_lp_solution_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_mip_chunked_header.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_mip_settings_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_mip_solution_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_pdlp_settings_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_populate_chunked_header_lp.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_populate_chunked_header_mip.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_problem_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_lp_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_mip_settings.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_mip_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_pdlp_settings.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_result_enums.proto.inc is excluded by !**/generated/**
📒 Files selected for processing (53)
  • GRPC_INTERFACE.md
  • GRPC_QUICK_START.md
  • GRPC_SERVER_ARCHITECTURE.md
  • build.sh
  • cpp/CMakeLists.txt
  • cpp/codegen/README.md
  • cpp/codegen/field_registry.yaml
  • cpp/codegen/generate_conversions.py
  • cpp/codegen/generated_baseline/cuopt_remote_data.proto
  • cpp/codegen/generated_baseline/generated_array_field_element_size.inc
  • cpp/codegen/generated_baseline/generated_build_array_chunks.inc
  • cpp/codegen/generated_baseline/generated_chunked_arrays_to_problem.inc
  • cpp/codegen/generated_baseline/generated_chunked_header_to_problem.inc
  • cpp/codegen/generated_baseline/generated_chunked_to_lp_solution.inc
  • cpp/codegen/generated_baseline/generated_chunked_to_mip_solution.inc
  • cpp/codegen/generated_baseline/generated_collect_lp_arrays.inc
  • cpp/codegen/generated_baseline/generated_collect_mip_arrays.inc
  • cpp/codegen/generated_baseline/generated_enum_converters.inc
  • cpp/codegen/generated_baseline/generated_estimate_lp_size.inc
  • cpp/codegen/generated_baseline/generated_estimate_mip_size.inc
  • cpp/codegen/generated_baseline/generated_estimate_problem_size.inc
  • cpp/codegen/generated_baseline/generated_lp_chunked_header.inc
  • cpp/codegen/generated_baseline/generated_lp_solution_to_proto.inc
  • cpp/codegen/generated_baseline/generated_mip_chunked_header.inc
  • cpp/codegen/generated_baseline/generated_mip_settings_to_proto.inc
  • cpp/codegen/generated_baseline/generated_mip_solution_to_proto.inc
  • cpp/codegen/generated_baseline/generated_pdlp_settings_to_proto.inc
  • cpp/codegen/generated_baseline/generated_populate_chunked_header_lp.inc
  • cpp/codegen/generated_baseline/generated_populate_chunked_header_mip.inc
  • cpp/codegen/generated_baseline/generated_problem_to_proto.inc
  • cpp/codegen/generated_baseline/generated_proto_to_lp_solution.inc
  • cpp/codegen/generated_baseline/generated_proto_to_mip_settings.inc
  • cpp/codegen/generated_baseline/generated_proto_to_mip_solution.inc
  • cpp/codegen/generated_baseline/generated_proto_to_pdlp_settings.inc
  • cpp/codegen/generated_baseline/generated_proto_to_problem.inc
  • cpp/codegen/generated_baseline/generated_result_enums.proto.inc
  • cpp/codegen/generated_baseline/generated_result_header_fields.proto.inc
  • cpp/include/cuopt/linear_programming/cpu_optimization_problem.hpp
  • cpp/src/grpc/client/grpc_client.cpp
  • cpp/src/grpc/client/grpc_client.hpp
  • cpp/src/grpc/cuopt_remote.proto
  • cpp/src/grpc/cuopt_remote_data.proto
  • cpp/src/grpc/cuopt_remote_service.proto
  • cpp/src/grpc/grpc_problem_mapper.cpp
  • cpp/src/grpc/grpc_settings_mapper.cpp
  • cpp/src/grpc/grpc_solution_mapper.cpp
  • cpp/src/grpc/grpc_solution_mapper.hpp
  • cpp/src/grpc/server/grpc_field_element_size.hpp
  • cpp/src/grpc/server/grpc_server_main.cpp
  • cpp/src/grpc/server/grpc_server_types.hpp
  • cpp/tests/linear_programming/grpc/CMakeLists.txt
  • cpp/tests/linear_programming/grpc/grpc_client_test.cpp
  • cpp/tests/linear_programming/grpc/grpc_pipe_serialization_test.cpp
💤 Files with no reviewable changes (1)
  • cpp/src/grpc/grpc_solution_mapper.hpp
✅ Files skipped from review due to trivial changes (26)
  • cpp/src/grpc/server/grpc_server_types.hpp
  • cpp/src/grpc/client/grpc_client.cpp
  • cpp/src/grpc/server/grpc_server_main.cpp
  • cpp/codegen/generated_baseline/generated_estimate_mip_size.inc
  • cpp/src/grpc/client/grpc_client.hpp
  • cpp/codegen/generated_baseline/generated_mip_solution_to_proto.inc
  • cpp/codegen/generated_baseline/generated_chunked_header_to_problem.inc
  • cpp/codegen/generated_baseline/generated_populate_chunked_header_lp.inc
  • cpp/codegen/generated_baseline/generated_lp_solution_to_proto.inc
  • cpp/codegen/generated_baseline/generated_collect_mip_arrays.inc
  • cpp/codegen/generated_baseline/generated_estimate_lp_size.inc
  • cpp/codegen/generated_baseline/generated_chunked_to_mip_solution.inc
  • cpp/codegen/generated_baseline/generated_populate_chunked_header_mip.inc
  • cpp/codegen/generated_baseline/generated_collect_lp_arrays.inc
  • cpp/codegen/generated_baseline/generated_build_array_chunks.inc
  • cpp/codegen/generated_baseline/generated_mip_settings_to_proto.inc
  • cpp/codegen/generated_baseline/generated_proto_to_mip_settings.inc
  • cpp/codegen/generated_baseline/generated_pdlp_settings_to_proto.inc
  • GRPC_QUICK_START.md
  • cpp/codegen/generated_baseline/generated_proto_to_pdlp_settings.inc
  • cpp/codegen/generated_baseline/generated_problem_to_proto.inc
  • cpp/codegen/generated_baseline/generated_chunked_to_lp_solution.inc
  • cpp/codegen/generated_baseline/generated_result_enums.proto.inc
  • cpp/codegen/generated_baseline/generated_proto_to_mip_solution.inc
  • cpp/codegen/generated_baseline/generated_array_field_element_size.inc
  • cpp/codegen/generated_baseline/generated_result_header_fields.proto.inc
🚧 Files skipped from review as they are similar to previous changes (10)
  • cpp/src/grpc/server/grpc_field_element_size.hpp
  • cpp/codegen/generated_baseline/generated_mip_chunked_header.inc
  • cpp/CMakeLists.txt
  • cpp/codegen/generated_baseline/generated_proto_to_lp_solution.inc
  • cpp/include/cuopt/linear_programming/cpu_optimization_problem.hpp
  • cpp/codegen/generated_baseline/generated_lp_chunked_header.inc
  • cpp/codegen/generated_baseline/generated_estimate_problem_size.inc
  • cpp/src/grpc/cuopt_remote_service.proto
  • GRPC_SERVER_ARCHITECTURE.md
  • cpp/codegen/generated_baseline/generated_enum_converters.inc

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
cpp/codegen/generate_conversions.py (3)

54-68: Consider using next(iter(...)) for single-key dict access.

The pattern list(entry.keys())[0] appears multiple times throughout the file (lines 58, 75, 100, 2079, 2091, 2119, 2151). Using next(iter(entry.keys())) is slightly more idiomatic and avoids creating an intermediate list.

♻️ Example refactor
-        name = list(entry.keys())[0]
+        name = next(iter(entry.keys()))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 54 - 68, Replace uses of
list(entry.keys())[0] (used when extracting the single key from a one-key dict
in the conversion logic that handles variable "entry") with next(iter(entry))
(or next(iter(entry.keys()))) to avoid creating an intermediate list; update
every occurrence in this file (including the shown block that returns {"name":
name, ...} and the other spots noted in the review) and keep the same assertion
that the dict has one key so behavior is unchanged.

1297-1297: Unused loop variable gname in setter_groups iteration.

This pattern appears at lines 1297, 1374, 1783, and 2022. Consider renaming to _gname to indicate intentional non-use, or use .values() if only the value is needed.

♻️ Example fix
-    for gname, gdef in setter_groups.items():
+    for _gname, gdef in setter_groups.items():

Or if gname is truly never needed:

-    for gname, gdef in setter_groups.items():
+    for gdef in setter_groups.values():
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` at line 1297, The for-loop uses an
unused variable name gname in "for gname, gdef in setter_groups.items()"; rename
gname to _gname (or iterate over setter_groups.values() as "for gdef in
setter_groups.values()") to signal intentional non-use and eliminate the
unused-variable warning, and apply the same change to the other identical loops
where "for gname, gdef in setter_groups.items()" appears.

1-19: Well-documented code generator with clear purpose.

The header and docstring clearly document the generator's purpose and output files. The licensing is correctly specified, though there's a duplicate SPDX-License-Identifier on lines 3 and 5.

🔧 Minor: Remove duplicate license identifier
 # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES.
-# SPDX-License-Identifier: Apache-2.0
 # All rights reserved.
 # SPDX-License-Identifier: Apache-2.0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 1 - 19, The file header
contains a duplicated SPDX-License-Identifier entry; remove the redundant line
so only a single "SPDX-License-Identifier: Apache-2.0" remains in the
top-of-file comments (leave the shebang and the SPDX-FileCopyrightText intact);
locate this in generate_conversions.py near the file header and delete the extra
SPDX-License-Identifier line.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cpp/codegen/generate_conversions.py`:
- Around line 1166-1171: The current logic in parse_field/getter derives
size_getter by blindly doing getter.replace("_host()", "_size()"), which breaks
when a field supplies a custom getter via the YAML `getter` key; update the code
to first check if the field dict `f` contains an explicit `size_getter` (or
`size_getter` YAML key) and use that when present, otherwise validate that
`getter` ends with "_host()" before performing the replacement and raise a clear
error if not; change the variable referenced in lines.append to use the chosen
`size_getter` (as used now) and ensure parse_field or the calling site
documents/accepts the new `size_getter` key so custom getters are handled
safely.

---

Nitpick comments:
In `@cpp/codegen/generate_conversions.py`:
- Around line 54-68: Replace uses of list(entry.keys())[0] (used when extracting
the single key from a one-key dict in the conversion logic that handles variable
"entry") with next(iter(entry)) (or next(iter(entry.keys()))) to avoid creating
an intermediate list; update every occurrence in this file (including the shown
block that returns {"name": name, ...} and the other spots noted in the review)
and keep the same assertion that the dict has one key so behavior is unchanged.
- Line 1297: The for-loop uses an unused variable name gname in "for gname, gdef
in setter_groups.items()"; rename gname to _gname (or iterate over
setter_groups.values() as "for gdef in setter_groups.values()") to signal
intentional non-use and eliminate the unused-variable warning, and apply the
same change to the other identical loops where "for gname, gdef in
setter_groups.items()" appears.
- Around line 1-19: The file header contains a duplicated
SPDX-License-Identifier entry; remove the redundant line so only a single
"SPDX-License-Identifier: Apache-2.0" remains in the top-of-file comments (leave
the shebang and the SPDX-FileCopyrightText intact); locate this in
generate_conversions.py near the file header and delete the extra
SPDX-License-Identifier line.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b4d5c993-698a-48f5-a46f-e19d86bbecff

📥 Commits

Reviewing files that changed from the base of the PR and between 42ad23f and f890f80.

⛔ Files ignored due to path filters (30)
  • cpp/codegen/generated/cuopt_remote_data.proto is excluded by !**/generated/**
  • cpp/codegen/generated/generated_array_field_element_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_build_array_chunks.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_arrays_to_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_header_to_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_to_lp_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_to_mip_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_collect_lp_arrays.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_collect_mip_arrays.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_enum_converters_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_enum_converters_settings.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_enum_converters_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_estimate_lp_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_estimate_mip_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_estimate_problem_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_lp_chunked_header.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_lp_solution_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_mip_chunked_header.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_mip_settings_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_mip_solution_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_pdlp_settings_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_populate_chunked_header_lp.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_populate_chunked_header_mip.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_problem_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_lp_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_mip_settings.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_mip_solution.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_pdlp_settings.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_result_enums.proto.inc is excluded by !**/generated/**
📒 Files selected for processing (24)
  • GRPC_INTERFACE.md
  • GRPC_QUICK_START.md
  • GRPC_SERVER_ARCHITECTURE.md
  • build.sh
  • cpp/CMakeLists.txt
  • cpp/codegen/README.md
  • cpp/codegen/field_registry.yaml
  • cpp/codegen/generate_conversions.py
  • cpp/include/cuopt/linear_programming/cpu_optimization_problem.hpp
  • cpp/src/grpc/client/grpc_client.cpp
  • cpp/src/grpc/client/grpc_client.hpp
  • cpp/src/grpc/cuopt_remote.proto
  • cpp/src/grpc/cuopt_remote_data.proto
  • cpp/src/grpc/cuopt_remote_service.proto
  • cpp/src/grpc/grpc_problem_mapper.cpp
  • cpp/src/grpc/grpc_settings_mapper.cpp
  • cpp/src/grpc/grpc_solution_mapper.cpp
  • cpp/src/grpc/grpc_solution_mapper.hpp
  • cpp/src/grpc/server/grpc_field_element_size.hpp
  • cpp/src/grpc/server/grpc_server_main.cpp
  • cpp/src/grpc/server/grpc_server_types.hpp
  • cpp/tests/linear_programming/grpc/CMakeLists.txt
  • cpp/tests/linear_programming/grpc/grpc_client_test.cpp
  • cpp/tests/linear_programming/grpc/grpc_pipe_serialization_test.cpp
💤 Files with no reviewable changes (1)
  • cpp/src/grpc/grpc_solution_mapper.hpp
✅ Files skipped from review due to trivial changes (7)
  • cpp/src/grpc/client/grpc_client.cpp
  • cpp/tests/linear_programming/grpc/CMakeLists.txt
  • build.sh
  • GRPC_QUICK_START.md
  • cpp/src/grpc/client/grpc_client.hpp
  • cpp/CMakeLists.txt
  • cpp/include/cuopt/linear_programming/cpu_optimization_problem.hpp
🚧 Files skipped from review as they are similar to previous changes (6)
  • cpp/src/grpc/server/grpc_server_main.cpp
  • cpp/src/grpc/server/grpc_server_types.hpp
  • cpp/src/grpc/server/grpc_field_element_size.hpp
  • cpp/tests/linear_programming/grpc/grpc_pipe_serialization_test.cpp
  • GRPC_SERVER_ARCHITECTURE.md
  • GRPC_INTERFACE.md

Comment on lines +1166 to +1171
f = parse_field(entry)
getter = f.get("getter", f"get_{f['name']}_host()")
size_getter = getter.replace("_host()", "_size()")
lines.append(
f"{ind}est += static_cast<size_t>(solution.{size_getter}) * sizeof(double);"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Size getter derivation relies on naming convention.

The transformation getter.replace("_host()", "_size()") at line 1168 assumes all host getters follow the *_host() pattern with a corresponding *_size() method. If a field uses a custom getter (via the getter YAML key), this replacement may produce an invalid method name.

Consider adding explicit size_getter support in the YAML schema or validating the derived name.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 1166 - 1171, The current
logic in parse_field/getter derives size_getter by blindly doing
getter.replace("_host()", "_size()"), which breaks when a field supplies a
custom getter via the YAML `getter` key; update the code to first check if the
field dict `f` contains an explicit `size_getter` (or `size_getter` YAML key)
and use that when present, otherwise validate that `getter` ends with "_host()"
before performing the replacement and raise a clear error if not; change the
variable referenced in lines.append to use the chosen `size_getter` (as used
now) and ensure parse_field or the calling site documents/accepts the new
`size_getter` key so custom getters are handled safely.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cpp/codegen/README.md`:
- Line 32: The markdown fenced code block opened at Line 32 in
cpp/codegen/README.md lacks a language tag causing markdownlint MD040; update
that opening fence to include a language identifier (e.g., "text" or "bash") so
the block is fenced as a specific language, keeping the rest of the block
content unchanged to satisfy the linter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 17775126-41c3-4556-9e2a-32c0c23ee5fb

📥 Commits

Reviewing files that changed from the base of the PR and between f890f80 and fc5fa78.

📒 Files selected for processing (9)
  • build.sh
  • ci/test_cpp.sh
  • conda/environments/all_cuda-129_arch-aarch64.yaml
  • conda/environments/all_cuda-129_arch-x86_64.yaml
  • conda/environments/all_cuda-131_arch-aarch64.yaml
  • conda/environments/all_cuda-131_arch-x86_64.yaml
  • conda/recipes/libcuopt/recipe.yaml
  • cpp/codegen/README.md
  • dependencies.yaml
✅ Files skipped from review due to trivial changes (1)
  • conda/recipes/libcuopt/recipe.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • build.sh


## File Layout

```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a language tag to the fenced block at Line 32.

Line 32 opens a fenced code block without a language, which triggers markdownlint MD040.

Suggested fix
-```
+```text
 cpp/codegen/
 ├── field_registry.yaml          # Source of truth for all fields
 ...
-```
+```
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 32-32: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/README.md` at line 32, The markdown fenced code block opened at
Line 32 in cpp/codegen/README.md lacks a language tag causing markdownlint
MD040; update that opening fence to include a language identifier (e.g., "text"
or "bash") so the block is fenced as a specific language, keeping the rest of
the block content unchanged to satisfy the linter.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (1)
cpp/codegen/generate_conversions.py (1)

1165-1171: ⚠️ Potential issue | 🟡 Minor

Don’t derive the size accessor by blind string replacement.

getter.replace("_host()", "_size()") only works for accessors that happen to follow that suffix convention. A valid YAML override with a different getter shape will emit bad generated code here. Either support an explicit size_getter override or fail fast when the getter does not end in _host().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 1165 - 1171, The code
generating size accessors currently derives size_getter by doing
getter.replace("_host()", "_size()"), which silently produces invalid code for
custom getter overrides; update generate_conversions.py to either read an
explicit size_getter from the parsed field (via parse_field result) when present
or validate the computed getter: check that the computed getter string (variable
getter from parse_field) endswith("_host()") and if not raise an explicit
error/exception (fail fast) explaining that a size_getter override is required;
adjust the loop over obj.get("arrays", []) where getter and size_getter are
computed and where lines.append(...) is emitted to use the explicit size_getter
or to abort with a clear message referencing parse_field, getter, and
size_getter.
🧹 Nitpick comments (1)
conda/environments/all_cuda-131_arch-x86_64.yaml (1)

63-64: Redundant pyyaml entries: unpinned and pinned versions coexist.

The generated file now contains both pyyaml (unpinned) and pyyaml>=6.0.0. While conda will resolve this correctly to >=6.0.0, the redundancy is a minor code smell. Per context snippet 1-3, this arises because build_cpp/test_cpp groups in dependencies.yaml define unpinned pyyaml while run_cuopt defines the pinned version.

Consider using the &pyyaml anchor (already defined in run_cuopt) in the build_cpp and test_cpp groups in dependencies.yaml to emit a single consistent entry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@conda/environments/all_cuda-131_arch-x86_64.yaml` around lines 63 - 64, The
conda env has redundant pyyaml entries; update dependencies.yaml so build_cpp
and test_cpp use the existing &pyyaml anchor from run_cuopt (rather than listing
an unpinned "pyyaml") so the generated all_cuda-131...yaml emits only the pinned
"pyyaml>=6.0.0" entry; modify the build_cpp and test_cpp group entries to
reference &pyyaml to ensure a single consistent pyyaml declaration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ci/verify_codegen.sh`:
- Around line 43-48: The verification currently skips when the generated tmp
proto exists but PROTO_DEST (cpp/src/grpc/cuopt_remote_data.proto) is missing;
change verify_codegen.sh so that if "${TMPDIR}/cuopt_remote_data.proto" exists
but "${PROTO_DEST}" does not, the script prints a missing-file error and sets
FAILED=1. Specifically, in the block referencing
"${TMPDIR}/cuopt_remote_data.proto" and "${PROTO_DEST}", add an explicit check
for the absence of PROTO_DEST (or invert the logic) to emit "MISMATCH:
cpp/src/grpc/cuopt_remote_data.proto (not copied from codegen)" and set FAILED=1
instead of silently skipping.
- Around line 28-41: The current loop over TMPDIR uses diff piped to head which
fails under set -o pipefail and aborts the script on first mismatch and it also
never detects stale files in GENERATED_DIR; modify the comparison so the diff
output is captured without letting the pipe exit non-zero terminate the loop
(e.g., run diff -u "${committed}" "$f" > some buffer or use diff ... | head -30
|| true) and explicitly set FAILED=1 when a mismatch is detected, and add a
second pass that iterates over files in "${GENERATED_DIR}" to flag any files
that do not have a counterpart in "${TMPDIR}" (use the same basename comparison
used for fname) so stale generated files are reported; update references to
TMPDIR, GENERATED_DIR, FAILED, and the diff invocation accordingly.

In `@cpp/codegen/generate_conversions.py`:
- Around line 2054-2076: The current _registry_has_field_numbers only detects if
any field_num/array_id exists but we must instead reject registries that are
partially numbered (some entries numbered, some None) unless the auto-numbering
flag is enabled; update _registry_has_field_numbers (and the analogous check
around the block referenced at lines 2320-2326) to scan each
section/scalars/arrays and settings (use _collect_field_nums and
_collect_settings_field_nums) and return a tri-state or raise/report an error
when mixed presence is found — i.e., if any numeric IDs exist but any entry
lacks a number, fail the generation unless the --auto-number option is active;
ensure checks inspect both top-level sections and warm_start subsections and
reference function names _registry_has_field_numbers, _collect_field_nums,
_collect_settings_field_nums, and the auto-number flag handler to gate the
rejection.
- Around line 1319-1349: The current unary setter group only checks the first
field's presence before calling the multi-array setter (using first_pname and
pb_problem.{first_pname}_size()), which allows partial/malformed data to pass;
change the guard to require that every non-None grouped field has data (i.e.,
pb_problem.{pname}_size() > 0 for all fields in fields that are not None) before
constructing the vectors and invoking cpu_problem.{setter_name}(...), mirroring
the chunked-path validation; update the if condition that uses first_pname to a
combined all(...) style check over each field's proto size so the unary
generator enforces presence of all member arrays.

In `@cpp/tests/linear_programming/grpc/grpc_client_test.cpp`:
- Around line 1750-1818: The test
SettingsMapperTest.MIPSettingsRoundTrip_AllFields omits the presolver field; set
original.presolver to a non-default presolver_t value (e.g., cast from an int
like 2) before mapping and add an EXPECT_EQ comparing restored.presolver to
original.presolver after map_proto_to_mip_settings; update the assertions in
this test to cover presolver so map_mip_settings_to_proto and
map_proto_to_mip_settings are exercised for the presolver conversion.

In `@docs/cuopt/grpc/GRPC_CODE_GENERATION.md`:
- Around line 20-24: The doc currently states "It runs automatically before
every `libcuopt` or `cuopt_grpc_server` build via `build.sh`", which is
misleading; change the wording to describe regeneration as an explicit
manual/codegen workflow step (e.g., "Regenerate the gRPC code by running the
generator as a separate `codegen` step or via `build.sh codegen` before
committing generated files") and update the related paragraphs (including the
block around lines 75–79) to instruct contributors to run the generator after
editing YAML and to re-run the codegen step when reviewing generated files;
mention the optional dependencies (`--auto-number`/`--strip` require
`ruamel.yaml`) and the runtime dependency (`PyYAML`) so readers know
prerequisites.

In `@docs/cuopt/grpc/GRPC_INTERFACE.md`:
- Around line 42-49: The docs incorrectly claim ChunkedProblemHeader carries
per-array metadata (ArrayDescriptor); update the description for
StartChunkedUpload/ChunkedProblemHeader to say it only includes scalar fields
(problem-level metadata) and not per-array descriptors, and change the bullet
about per-array metadata to reference ArrayChunk (which contains field_id,
element_offset, total_elements) as the place implementers should read those
fields; keep mention that the server reports max_message_bytes and that
FinishChunkedUpload triggers reassembly/job submission.
- Line 25: Add the missing language tag to the fenced code blocks that contain
the ASCII protocol diagrams in GRPC_INTERFACE.md so markdownlint MD040 stops
firing: change the opening triple-backticks for each ASCII diagram block to
include the language tag "text" (i.e., ```text) for both diagram fences
referenced in the comment.

---

Duplicate comments:
In `@cpp/codegen/generate_conversions.py`:
- Around line 1165-1171: The code generating size accessors currently derives
size_getter by doing getter.replace("_host()", "_size()"), which silently
produces invalid code for custom getter overrides; update
generate_conversions.py to either read an explicit size_getter from the parsed
field (via parse_field result) when present or validate the computed getter:
check that the computed getter string (variable getter from parse_field)
endswith("_host()") and if not raise an explicit error/exception (fail fast)
explaining that a size_getter override is required; adjust the loop over
obj.get("arrays", []) where getter and size_getter are computed and where
lines.append(...) is emitted to use the explicit size_getter or to abort with a
clear message referencing parse_field, getter, and size_getter.

---

Nitpick comments:
In `@conda/environments/all_cuda-131_arch-x86_64.yaml`:
- Around line 63-64: The conda env has redundant pyyaml entries; update
dependencies.yaml so build_cpp and test_cpp use the existing &pyyaml anchor from
run_cuopt (rather than listing an unpinned "pyyaml") so the generated
all_cuda-131...yaml emits only the pinned "pyyaml>=6.0.0" entry; modify the
build_cpp and test_cpp group entries to reference &pyyaml to ensure a single
consistent pyyaml declaration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1b2786bd-eec5-455b-bf3e-e0d02c74e8ba

📥 Commits

Reviewing files that changed from the base of the PR and between fc5fa78 and c0b12d8.

⛔ Files ignored due to path filters (5)
  • cpp/codegen/generated/generated_build_array_chunks.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_chunked_arrays_to_problem.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_estimate_problem_size.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_problem_to_proto.inc is excluded by !**/generated/**
  • cpp/codegen/generated/generated_proto_to_problem.inc is excluded by !**/generated/**
📒 Files selected for processing (14)
  • GRPC_INTERFACE.md
  • ci/verify_codegen.sh
  • conda/environments/all_cuda-129_arch-aarch64.yaml
  • conda/environments/all_cuda-129_arch-x86_64.yaml
  • conda/environments/all_cuda-131_arch-aarch64.yaml
  • conda/environments/all_cuda-131_arch-x86_64.yaml
  • cpp/CMakeLists.txt
  • cpp/codegen/field_registry.yaml
  • cpp/codegen/generate_conversions.py
  • cpp/tests/linear_programming/grpc/CMakeLists.txt
  • cpp/tests/linear_programming/grpc/grpc_client_test.cpp
  • dependencies.yaml
  • docs/cuopt/grpc/GRPC_CODE_GENERATION.md
  • docs/cuopt/grpc/GRPC_INTERFACE.md
💤 Files with no reviewable changes (1)
  • GRPC_INTERFACE.md
✅ Files skipped from review due to trivial changes (3)
  • conda/environments/all_cuda-129_arch-aarch64.yaml
  • conda/environments/all_cuda-131_arch-aarch64.yaml
  • cpp/tests/linear_programming/grpc/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (3)
  • conda/environments/all_cuda-129_arch-x86_64.yaml
  • cpp/CMakeLists.txt
  • dependencies.yaml

Comment on lines +28 to +41
for f in "${TMPDIR}"/*; do
fname=$(basename "$f")
committed="${GENERATED_DIR}/${fname}"
if [ ! -f "${committed}" ]; then
echo "MISSING: ${committed} (new generated file not committed)"
FAILED=1
continue
fi
if ! diff -q "$f" "${committed}" > /dev/null 2>&1; then
echo "MISMATCH: cpp/codegen/generated/${fname}"
diff -u "${committed}" "$f" | head -30
FAILED=1
fi
done
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the directory comparison exhaustive under pipefail.

diff -u "${committed}" "$f" | head -30 returns non-zero on a real diff when set -o pipefail is enabled, so this block exits on the first mismatch before FAILED=1 is recorded or the rest of the generated files are checked. The loop also only walks ${TMPDIR}, so stale files that still exist in cpp/codegen/generated/ are never reported.

Suggested fix
 for f in "${TMPDIR}"/*; do
     fname=$(basename "$f")
     committed="${GENERATED_DIR}/${fname}"
     if [ ! -f "${committed}" ]; then
         echo "MISSING: ${committed} (new generated file not committed)"
         FAILED=1
         continue
     fi
     if ! diff -q "$f" "${committed}" > /dev/null 2>&1; then
         echo "MISMATCH: cpp/codegen/generated/${fname}"
-        diff -u "${committed}" "$f" | head -30
+        diff -u "${committed}" "$f" | head -30 || true
         FAILED=1
     fi
 done
+
+for committed in "${GENERATED_DIR}"/*; do
+    fname=$(basename "${committed}")
+    if [ ! -f "${TMPDIR}/${fname}" ]; then
+        echo "STALE: ${committed} (committed file is no longer generated)"
+        FAILED=1
+    fi
+done
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for f in "${TMPDIR}"/*; do
fname=$(basename "$f")
committed="${GENERATED_DIR}/${fname}"
if [ ! -f "${committed}" ]; then
echo "MISSING: ${committed} (new generated file not committed)"
FAILED=1
continue
fi
if ! diff -q "$f" "${committed}" > /dev/null 2>&1; then
echo "MISMATCH: cpp/codegen/generated/${fname}"
diff -u "${committed}" "$f" | head -30
FAILED=1
fi
done
for f in "${TMPDIR}"/*; do
fname=$(basename "$f")
committed="${GENERATED_DIR}/${fname}"
if [ ! -f "${committed}" ]; then
echo "MISSING: ${committed} (new generated file not committed)"
FAILED=1
continue
fi
if ! diff -q "$f" "${committed}" > /dev/null 2>&1; then
echo "MISMATCH: cpp/codegen/generated/${fname}"
diff -u "${committed}" "$f" | head -30 || true
FAILED=1
fi
done
for committed in "${GENERATED_DIR}"/*; do
fname=$(basename "${committed}")
if [ ! -f "${TMPDIR}/${fname}" ]; then
echo "STALE: ${committed} (committed file is no longer generated)"
FAILED=1
fi
done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ci/verify_codegen.sh` around lines 28 - 41, The current loop over TMPDIR uses
diff piped to head which fails under set -o pipefail and aborts the script on
first mismatch and it also never detects stale files in GENERATED_DIR; modify
the comparison so the diff output is captured without letting the pipe exit
non-zero terminate the loop (e.g., run diff -u "${committed}" "$f" > some buffer
or use diff ... | head -30 || true) and explicitly set FAILED=1 when a mismatch
is detected, and add a second pass that iterates over files in
"${GENERATED_DIR}" to flag any files that do not have a counterpart in
"${TMPDIR}" (use the same basename comparison used for fname) so stale generated
files are reported; update references to TMPDIR, GENERATED_DIR, FAILED, and the
diff invocation accordingly.

Comment on lines +43 to +48
if [ -f "${TMPDIR}/cuopt_remote_data.proto" ] && [ -f "${PROTO_DEST}" ]; then
if ! diff -q "${TMPDIR}/cuopt_remote_data.proto" "${PROTO_DEST}" > /dev/null 2>&1; then
echo "MISMATCH: cpp/src/grpc/cuopt_remote_data.proto (not copied from codegen)"
FAILED=1
fi
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail when the copied proto is missing.

generate_conversions.py always writes cuopt_remote_data.proto into ${TMPDIR}, so the only time this block skips is when cpp/src/grpc/cuopt_remote_data.proto is missing. That should fail CI like any other missing generated artifact; otherwise deleting the copied proto passes verification silently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ci/verify_codegen.sh` around lines 43 - 48, The verification currently skips
when the generated tmp proto exists but PROTO_DEST
(cpp/src/grpc/cuopt_remote_data.proto) is missing; change verify_codegen.sh so
that if "${TMPDIR}/cuopt_remote_data.proto" exists but "${PROTO_DEST}" does not,
the script prints a missing-file error and sets FAILED=1. Specifically, in the
block referencing "${TMPDIR}/cuopt_remote_data.proto" and "${PROTO_DEST}", add
an explicit check for the absence of PROTO_DEST (or invert the logic) to emit
"MISMATCH: cpp/src/grpc/cuopt_remote_data.proto (not copied from codegen)" and
set FAILED=1 instead of silently skipping.

Comment on lines +1319 to +1349
# Setter groups — guard on first field having data
for gname, gdef in setter_groups.items():
setter_name = gdef["setter"]
fields = [
_find_problem_field(obj, fn) for fn in gdef.get("fields", [])
]
first = next((f for f in fields if f is not None), None)
if first is None:
continue
first_pname = _proto_cpp_name(first["name"])
lines.append(f"{ind}if (pb_problem.{first_pname}_size() > 0) {{")
ii = ind + " "

for f in fields:
if f is None:
continue
pname = _proto_cpp_name(f["name"])
ftype = f.get("type", "repeated double")
cpp_t = _array_cpp_type(ftype)
lines.append(
f"{ii}std::vector<{cpp_t}> {f['name']}(pb_problem.{pname}().begin(), pb_problem.{pname}().end());"
)

args = []
for f in fields:
if f is None:
continue
args.append(f"{f['name']}.data()")
args.append(f"static_cast<i_t>({f['name']}.size())")
lines.append(f"{ii}cpu_problem.{setter_name}({', '.join(args)});")
lines.append(f"{ind}}}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unary setter groups should require all member arrays.

This path only checks the first grouped field before calling the multi-array setter. A malformed unary OptimizationProblem with A_values present but A_indices / A_offsets missing will still call set_csr_constraint_matrix(...) with partial data. The chunked path later in this file already guards on every grouped field, so the unary generator should enforce the same rule.

Suggested fix
-        first_pname = _proto_cpp_name(first["name"])
-        lines.append(f"{ind}if (pb_problem.{first_pname}_size() > 0) {{")
+        guard_parts = [
+            f"pb_problem.{_proto_cpp_name(f['name'])}_size() > 0"
+            for f in fields
+            if f is not None
+        ]
+        lines.append(f"{ind}if ({' && '.join(guard_parts)}) {{")

As per coding guidelines, "**/*.{cpp,hpp,h}: Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files".

🧰 Tools
🪛 Ruff (0.15.7)

[warning] 1320-1320: Loop control variable gname not used within loop body

Rename unused gname to _gname

(B007)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 1319 - 1349, The current
unary setter group only checks the first field's presence before calling the
multi-array setter (using first_pname and pb_problem.{first_pname}_size()),
which allows partial/malformed data to pass; change the guard to require that
every non-None grouped field has data (i.e., pb_problem.{pname}_size() > 0 for
all fields in fields that are not None) before constructing the vectors and
invoking cpu_problem.{setter_name}(...), mirroring the chunked-path validation;
update the if condition that uses first_pname to a combined all(...) style check
over each field's proto size so the unary generator enforces presence of all
member arrays.

Comment on lines +2054 to +2076
def _registry_has_field_numbers(registry):
"""Check if the registry has at least one field_num or array_id assigned."""
for section_key in list(registry.keys()):
section = registry.get(section_key)
if not isinstance(section, dict):
continue
for list_key in ["scalars", "arrays"]:
for nums in [
_collect_field_nums(section.get(list_key, []), "field_num"),
_collect_field_nums(section.get(list_key, []), "array_id"),
]:
if nums:
return True
if "fields" in section:
if _collect_settings_field_nums(section["fields"]):
return True
sub = section.get("warm_start")
if isinstance(sub, dict):
for list_key in ["scalars", "arrays"]:
for key in ["field_num", "array_id"]:
if _collect_field_nums(sub.get(list_key, []), key):
return True
return False
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject partially numbered registries before generating.

The preflight only checks whether any field_num / array_id exists. Every generator below silently skips entries whose numbers are None, so adding one unnumbered field produces incomplete .proto / .inc outputs with no error. This should fail unless --auto-number is being used.

Also applies to: 2320-2326

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/codegen/generate_conversions.py` around lines 2054 - 2076, The current
_registry_has_field_numbers only detects if any field_num/array_id exists but we
must instead reject registries that are partially numbered (some entries
numbered, some None) unless the auto-numbering flag is enabled; update
_registry_has_field_numbers (and the analogous check around the block referenced
at lines 2320-2326) to scan each section/scalars/arrays and settings (use
_collect_field_nums and _collect_settings_field_nums) and return a tri-state or
raise/report an error when mixed presence is found — i.e., if any numeric IDs
exist but any entry lacks a number, fail the generation unless the --auto-number
option is active; ensure checks inspect both top-level sections and warm_start
subsections and reference function names _registry_has_field_numbers,
_collect_field_nums, _collect_settings_field_nums, and the auto-number flag
handler to gate the rejection.

Comment on lines +1750 to +1818
TEST(SettingsMapperTest, MIPSettingsRoundTrip_AllFields)
{
mip_solver_settings_t<int32_t, double> original;
original.time_limit = 42.5;
original.tolerances.relative_mip_gap = 0.01;
original.tolerances.absolute_mip_gap = 1e-8;
original.tolerances.integrality_tolerance = 1e-6;
original.tolerances.absolute_tolerance = 1e-7;
original.tolerances.relative_tolerance = 1e-13;
original.tolerances.presolve_absolute_tolerance = 1e-5;
original.log_to_console = false;
original.heuristics_only = true;
original.num_cpu_threads = 8;
original.num_gpus = 2;
original.mip_scaling = true;

original.work_limit = 100.0;
original.node_limit = 5000;
original.reliability_branching = 3;
original.mip_batch_pdlp_strong_branching = 1;
original.max_cut_passes = 20;
original.mir_cuts = 2;
original.mixed_integer_gomory_cuts = 1;
original.knapsack_cuts = 0;
original.clique_cuts = 3;
original.strong_chvatal_gomory_cuts = -1;
original.reduced_cost_strengthening = 1;
original.cut_change_threshold = 0.05;
original.cut_min_orthogonality = 0.3;
original.determinism_mode = 1;
original.seed = 12345;

cuopt::remote::MIPSolverSettings proto;
map_mip_settings_to_proto(original, &proto);

mip_solver_settings_t<int32_t, double> restored;
map_proto_to_mip_settings(proto, restored);

EXPECT_DOUBLE_EQ(restored.time_limit, original.time_limit);
EXPECT_DOUBLE_EQ(restored.tolerances.relative_mip_gap, original.tolerances.relative_mip_gap);
EXPECT_DOUBLE_EQ(restored.tolerances.absolute_mip_gap, original.tolerances.absolute_mip_gap);
EXPECT_DOUBLE_EQ(restored.tolerances.integrality_tolerance,
original.tolerances.integrality_tolerance);
EXPECT_DOUBLE_EQ(restored.tolerances.absolute_tolerance, original.tolerances.absolute_tolerance);
EXPECT_DOUBLE_EQ(restored.tolerances.relative_tolerance, original.tolerances.relative_tolerance);
EXPECT_DOUBLE_EQ(restored.tolerances.presolve_absolute_tolerance,
original.tolerances.presolve_absolute_tolerance);
EXPECT_EQ(restored.log_to_console, original.log_to_console);
EXPECT_EQ(restored.heuristics_only, original.heuristics_only);
EXPECT_EQ(restored.num_cpu_threads, original.num_cpu_threads);
EXPECT_EQ(restored.num_gpus, original.num_gpus);
EXPECT_EQ(restored.mip_scaling, original.mip_scaling);

EXPECT_DOUBLE_EQ(restored.work_limit, original.work_limit);
EXPECT_EQ(restored.node_limit, original.node_limit);
EXPECT_EQ(restored.reliability_branching, original.reliability_branching);
EXPECT_EQ(restored.mip_batch_pdlp_strong_branching, original.mip_batch_pdlp_strong_branching);
EXPECT_EQ(restored.max_cut_passes, original.max_cut_passes);
EXPECT_EQ(restored.mir_cuts, original.mir_cuts);
EXPECT_EQ(restored.mixed_integer_gomory_cuts, original.mixed_integer_gomory_cuts);
EXPECT_EQ(restored.knapsack_cuts, original.knapsack_cuts);
EXPECT_EQ(restored.clique_cuts, original.clique_cuts);
EXPECT_EQ(restored.strong_chvatal_gomory_cuts, original.strong_chvatal_gomory_cuts);
EXPECT_EQ(restored.reduced_cost_strengthening, original.reduced_cost_strengthening);
EXPECT_DOUBLE_EQ(restored.cut_change_threshold, original.cut_change_threshold);
EXPECT_DOUBLE_EQ(restored.cut_min_orthogonality, original.cut_min_orthogonality);
EXPECT_EQ(restored.determinism_mode, original.determinism_mode);
EXPECT_EQ(restored.seed, original.seed);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

MIPSettingsRoundTrip_AllFields is missing presolver.

This test never sets or asserts the presolver round-trip, even though the generated MIP mapper has a dedicated int32presolver_t cast path. A regression there would slip through while this test still advertises “AllFields”.

Suggested fix
 TEST(SettingsMapperTest, MIPSettingsRoundTrip_AllFields)
 {
   mip_solver_settings_t<int32_t, double> original;
   original.time_limit                             = 42.5;
@@
   original.num_cpu_threads                        = 8;
   original.num_gpus                               = 2;
+  original.presolver                              = presolver_t::Papilo;
   original.mip_scaling                            = true;
@@
   EXPECT_EQ(restored.num_cpu_threads, original.num_cpu_threads);
   EXPECT_EQ(restored.num_gpus, original.num_gpus);
+  EXPECT_EQ(static_cast<int>(restored.presolver), static_cast<int>(original.presolver));
   EXPECT_EQ(restored.mip_scaling, original.mip_scaling);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/tests/linear_programming/grpc/grpc_client_test.cpp` around lines 1750 -
1818, The test SettingsMapperTest.MIPSettingsRoundTrip_AllFields omits the
presolver field; set original.presolver to a non-default presolver_t value
(e.g., cast from an int like 2) before mapping and add an EXPECT_EQ comparing
restored.presolver to original.presolver after map_proto_to_mip_settings; update
the assertions in this test to cover presolver so map_mip_settings_to_proto and
map_proto_to_mip_settings are exercised for the presolver conversion.

Comment on lines +20 to +24
The generator runs in ~100ms with no external dependencies beyond PyYAML (ships
with conda). The `--auto-number` and `--strip` options additionally require
`ruamel.yaml` (listed in the project's development dependencies). It runs
automatically before every `libcuopt` or `cuopt_grpc_server` build via
`build.sh`.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Describe regeneration as an explicit step, not an automatic one.

This says the generator runs before every libcuopt / cuopt_grpc_server build, but the workflow in this PR is “edit YAML → rerun codegen → review generated files”, and the build integration is a dedicated codegen step. Leaving it this way will make contributors assume a normal build refreshes committed outputs when it doesn’t.

As per coding guidelines, "docs/**/*: For documentation changes, focus on: Accuracy: Verify code examples compile and run correctly".

Also applies to: 75-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/cuopt/grpc/GRPC_CODE_GENERATION.md` around lines 20 - 24, The doc
currently states "It runs automatically before every `libcuopt` or
`cuopt_grpc_server` build via `build.sh`", which is misleading; change the
wording to describe regeneration as an explicit manual/codegen workflow step
(e.g., "Regenerate the gRPC code by running the generator as a separate
`codegen` step or via `build.sh codegen` before committing generated files") and
update the related paragraphs (including the block around lines 75–79) to
instruct contributors to run the generator after editing YAML and to re-run the
codegen step when reviewing generated files; mention the optional dependencies
(`--auto-number`/`--strip` require `ruamel.yaml`) and the runtime dependency
(`PyYAML`) so readers know prerequisites.

When the estimated serialized problem size exceeds 75% of `max_message_bytes`, the client
splits large arrays into chunks and sends them via multiple unary RPCs:

```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language tags to the diagram fences.

markdownlint MD040 is firing on both fenced blocks. text is enough for these ASCII protocol diagrams.

Also applies to: 56-56

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 25-25: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/cuopt/grpc/GRPC_INTERFACE.md` at line 25, Add the missing language tag
to the fenced code blocks that contain the ASCII protocol diagrams in
GRPC_INTERFACE.md so markdownlint MD040 stops firing: change the opening
triple-backticks for each ASCII diagram block to include the language tag "text"
(i.e., ```text) for both diagram fences referenced in the comment.

Comment on lines +42 to +49
**Key features:**
- `StartChunkedUpload` sends a `ChunkedProblemHeader` with all scalar fields and
array metadata (`ArrayDescriptor` for each large array: field ID, total elements,
element size)
- Each `SendArrayChunk` carries one chunk of one array, identified by `ArrayFieldId`
and `element_offset`
- The server reports `max_message_bytes` so the client can adapt chunk sizing
- `FinishChunkedUpload` triggers server-side reassembly and job submission
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

ChunkedProblemHeader doesn’t carry array descriptors.

The current wording says StartChunkedUpload includes per-array metadata in the header, but the proto places field_id, element_offset, and total_elements on ArrayChunk, not on ChunkedProblemHeader. As written, this points implementers at the wrong message when they wire upload support.

As per coding guidelines, "docs/**/*: For documentation changes, focus on: Accuracy: Verify code examples compile and run correctly".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/cuopt/grpc/GRPC_INTERFACE.md` around lines 42 - 49, The docs incorrectly
claim ChunkedProblemHeader carries per-array metadata (ArrayDescriptor); update
the description for StartChunkedUpload/ChunkedProblemHeader to say it only
includes scalar fields (problem-level metadata) and not per-array descriptors,
and change the bullet about per-array metadata to reference ArrayChunk (which
contains field_id, element_offset, total_elements) as the place implementers
should read those fields; keep mention that the server reports max_message_bytes
and that FinishChunkedUpload triggers reassembly/job submission.

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

Labels

improvement Improves an existing functionality non-breaking Introduces a non-breaking change P1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant