From a8a1fc7cf48fa573a836ea7b62455e2944b5402c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:26:29 +0000 Subject: [PATCH] docs: Add comprehensive plan for upgrading C core from 0.10.17 to 1.0.1 Agent-Logs-Url: https://github.com/igraph/rigraph/sessions/0948b416-3c4a-44dc-bc60-6c25876ca69e Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- doc/c-core-upgrade-1.0.1-plan.md | 538 +++++++++++++++++++++++++++++++ 1 file changed, 538 insertions(+) create mode 100644 doc/c-core-upgrade-1.0.1-plan.md diff --git a/doc/c-core-upgrade-1.0.1-plan.md b/doc/c-core-upgrade-1.0.1-plan.md new file mode 100644 index 00000000000..4124470d432 --- /dev/null +++ b/doc/c-core-upgrade-1.0.1-plan.md @@ -0,0 +1,538 @@ +# Plan: Upgrading the C Core from 0.10.17 to 1.0.1 + +## Executive Summary + +The igraph C library 1.0.0 (released December 2025) introduces **significant +API-breaking changes** after nearly twenty years of igraph releases. Upgrading +rigraph from C core 0.10.17 to 1.0.1 involves 760 upstream commits, touching +every layer of the R–C interface. This document analyzes the scope of changes, +proposes strategies to subdivide the work, and evaluates the feasibility of +each approach—including an "agentic history rewrite" option. + +## 1. Current State + +| Component | Details | +|---|---| +| C core version | 0.10.17 (vendored in `src/vendor/cigraph/`) | +| Target version | 1.0.1 | +| Commits between versions | 760 (first-parent) | +| Intermediate tags | `1.0.0-rc1` (631 commits from 0.10.17), `1.0.0` (84 more), `1.0.1` (45 more) | +| Vendoring mechanism | `vendor.sh` / `vendor-one.sh` → cmake → patches → `Makefile-cigraph` → Stimulus | +| Autogenerated R bindings | ~107 functions in `functions-R.yaml` → `R/aaa-auto.R` + `src/rinterface.c` | +| C functions in interface | ~490 defined in `functions.yaml` | +| Total test cases | ~1,505 across 58 test files | +| Snapshot tests | 63 snapshot files (230 KB for `aaa-auto.md` alone) | + +## 2. Scope of Breaking Changes + +### 2.1 Type Renames in `types.yaml` + +These are mechanical renames that affect both the upstream `types.yaml` and +the R-side `types-RC.yaml` / `types-RR.yaml`: + +| Old Name | New Name | +|---|---| +| `EDGEWEIGHTS` | `EDGE_WEIGHTS` | +| `VERTEXWEIGHTS` | `VERTEX_WEIGHTS` | +| `EDGE_CAPACITY` | `EDGE_CAPACITIES` | +| `EDGE_COLOR` | `EDGE_COLORS` | +| `VERTEX_COLOR` | `VERTEX_COLORS` | +| `EDGESET_LIST` | `EDGE_INDICES_LIST` | +| `VERTEXSET_LIST` | `VERTEX_INDICES_LIST` | +| `ARPACKOPT` | `ARPACK_OPTIONS` | +| `ARPACKSTORAGE` | `ARPACK_STORAGE` | +| `ARPACKFUNC` | `ARPACK_FUNC` | +| `LEVCFUNC` | `LEVC_FUNC` | + +**Impact**: ~21 references across `types-RC.yaml` and `types-RR.yaml`. Purely +mechanical, can be done with search-and-replace. + +### 2.2 Removed Types + +| Removed | Notes | +|---|---| +| `LONGINT` | No longer needed | +| `ERDOS_RENYI_TYPE` | ER games reworked with `EDGE_TYPE_SW` | +| `IMITATE_ALGORITHM` | Stochastic imitation functions removed | +| `OPTIMALITY` | Deterministic optimal imitation removed | +| `REWIRING_MODE` | Replaced by `EDGE_TYPE_SW` | + +### 2.3 New Types + +| New Type | Purpose | +|---|---| +| `EDGE_TYPE_SW` | Replaces multiple `loops`/`multiple` booleans | +| `LEIDEN_OBJECTIVE` | For `igraph_community_leiden_simple()` | +| `METRIC` | For spatial network algorithms | +| `MSTALGORITHM` | MST algorithm selection | +| `LPA_VARIANT` | Label propagation variant | +| `REWIRING_STATS` | Statistics for edge rewiring | + +### 2.4 Function Signature Changes (~2,150 diff lines in `functions.yaml`) + +**Categories of changes:** + +1. **Parameter additions** (most common): `weights` added to `igraph_distances()`, + `igraph_diameter()`, `igraph_get_shortest_path()`, etc.; `loops`/`multiple` + added to `igraph_neighbors()`, `igraph_incident()`; `normalized` added to + betweenness functions; `bycol` added to `igraph_edges()`. + +2. **Parameter type changes**: `BOOLEAN loops` → `LOOPS loops` in degree/strength + functions; `BOOLEAN loops, BOOLEAN multiple` → `EDGE_TYPE_SW allowed_edge_types` + in game generators. + +3. **Parameter reordering**: `igraph_correlated_game()` swapped first two args; + various functions moved `weights` to position 2. + +4. **Function renames**: `igraph_delete_vertices_idx` → `igraph_delete_vertices_map`; + `igraph_lcf_vector` → `igraph_lcf`; `igraph_count_automorphisms` → + `igraph_count_automorphisms_bliss` (+ new simplified version). + +5. **Function removals**: ~30 deprecated functions removed (see Section 2.6). + +6. **New functions**: ~20 new functions added (spatial networks, `igraph_setup()`, + `igraph_iea_game()`, `igraph_community_leiden_simple()`, etc.). + +### 2.5 Attribute Handler Changes (Critical) + +The `rinterface_extra.c` file contains rigraph's custom attribute handler +(~111 references to `igraph_attribute`). Key breaking changes: + +- `igraph_attribute_handler_t` members now take `igraph_attribute_record_list_t` + instead of `igraph_vector_ptr_t`. +- `igraph_attribute_record_t.value` is now a union instead of `void*`. +- `gettype` → `get_type` in the attribute table struct. +- Attribute retrieval must append to result vectors instead of clearing them. +- `IGRAPH_ATTRIBUTE_DEFAULT` enum value removed. + +**This is the single largest source of manual work**, as the attribute handler +is hand-written C code specific to rigraph and cannot be autogenerated. + +### 2.6 Removed Deprecated Functions + +The following functions were removed in 1.0.0. Any R wrappers or tests using +them must be updated: + +- `igraph_are_connected()` → `igraph_are_adjacent()` +- `igraph_adjacent_triangles()` → `igraph_count_adjacent_triangles()` +- `igraph_automorphisms()` → `igraph_count_automorphisms()` +- `igraph_convex_hull()` → `igraph_convex_hull_2d()` +- `igraph_hub_score()` / `igraph_authority_score()` → `igraph_hub_and_authority_scores()` +- `igraph_erdos_renyi_game()` / `igraph_bipartite_game()` → `_gnm()` / `_gnp()` variants +- `igraph_tree()` → `igraph_kary_tree()` +- `igraph_lattice()` → `igraph_square_lattice()` +- `igraph_random_edge_walk()` → `igraph_random_walk()` +- `igraph_transitive_closure_dag()` → `igraph_transitive_closure()` +- Various `igraph_sparsemat*()`, `igraph_vector_copy()`, `igraph_matrix_copy()`, etc. +- `igraph_sample_dirichlet()`, `igraph_sample_sphere_*()` → `igraph_rng_sample_*()` +- `igraph_diameter_dijkstra()` → merged into `igraph_diameter()` with `weights` parameter +- `igraph_average_path_length_dijkstra()` → merged similarly +- All `*_dijkstra` variants of eccentricity, radius, graph_center, pseudo_diameter + +### 2.7 Interruption Handler Changes + +- Handlers no longer take `void *` argument. +- Return type changed from `igraph_error_t` to `igraph_bool_t`. +- `RNG_BEGIN()` / `RNG_END()` macros removed. + +These affect `rrandom.c` and potentially `rinterface_extra.c`. + +### 2.8 Behavioral Changes Affecting Tests + +- Pajek reader/writer now maps vertex labels to `name` (was `id`). +- `igraph_community_edge_betweenness()` treats large weights as strong connections. +- `igraph_biadjacency()` truncates instead of rounding. +- Edge ordering from `igraph_adjacency()` and related functions changed. +- `igraph_eigenvector_centrality()` warns about zero centralities. +- `igraph_permute_vertices()` semantics inverted. +- Various default value changes (e.g., `cutoff=-1` → `cutoff=UNLIMITED`). + +## 3. Proposed Approaches + +### 3.1 Approach A: "Big Bang" Vendor + Fix + +**Description**: Vendor 1.0.1 in one shot, then fix all compilation errors +and test failures. + +**Pros**: Simplest conceptually; no intermediate states to maintain. + +**Cons**: This is what was previously attempted and failed. The change is too +large to debug effectively. The attribute handler rewrite alone is estimated +at 500+ lines of careful C code changes. Combined with ~45 function signature +changes in Stimulus configs and ~1,500 test cases potentially affected, the +debugging surface is enormous. + +**Verdict**: ❌ Not recommended as primary approach. + +### 3.2 Approach B: Vendor + Skip Failing Tests + +**Description**: Vendor 1.0.1, fix compilation errors (attribute handler, +type renames, function signatures), but skip all failing R tests and examples. +Then restore tests one-by-one in subsequent PRs. + +**Steps**: +1. Vendor the 1.0.1 C sources. +2. Update `types-RC.yaml` and `types-RR.yaml` with type renames. +3. Update `functions-R.yaml` for changed/removed/renamed functions. +4. Rewrite the attribute handler in `rinterface_extra.c`. +5. Update `rrandom.c` for RNG/interrupt handler changes. +6. Run `make -f Makefile-cigraph` to regenerate `rinterface.c` and `aaa-auto.R`. +7. Fix compilation errors. +8. Run tests, skip all failures with `skip("TODO: Fix after C core 1.0.1 upgrade")`. +9. Document the full list of skipped tests. +10. Merge the "broken but compiling" state. +11. Fix skipped tests one-by-one in subsequent PRs. + +**Pros**: Gets the C core updated quickly. Creates an actionable backlog of +test fixes. Each subsequent fix is small and reviewable. + +**Cons**: Temporarily ships with known regressions. Requires careful triage +to distinguish "test needs updating" from "genuine bug introduced". + +**Effort estimate**: +- Steps 1–8: 2–4 days of focused work (attribute handler is the bottleneck). +- Step 11: 1–3 weeks spread over multiple PRs. + +**Verdict**: ✅ **Recommended as primary approach**. + +### 3.3 Approach C: Incremental Vendoring via Intermediate Commits + +**Description**: Use `vendor-one.sh` to import commits one-by-one from +0.10.17 to 1.0.1, fixing breakages as they appear. + +**Analysis**: `vendor-one.sh` already supports this—it iterates over commits +and stops at the first one that produces a diff. However: + +- There are 760 first-parent commits between 0.10.17 and 1.0.1. +- The breaking changes were mostly introduced in a small number of large merge + commits (the 1.0.0 development was done on the `develop` branch). +- Most commits between 0.10.17 and 1.0.0-rc1 are on the `develop` branch + and were merged en masse. The breaking API changes don't land incrementally— + they land as large merge commits. + +**Key problem**: The upstream history doesn't have a clean series of +individually-buildable commits that gradually introduce breaking changes. +The API changes were developed in parallel on feature branches and merged. + +**Verdict**: ⚠️ Partially viable. Could be used for the 1.0.0→1.0.1 segment +(45 commits, mostly bug fixes), but not effective for the main 0.10.17→1.0.0 +transition. + +### 3.4 Approach D: "Rewritten History" — Curated Commit Series + +**Description**: Create a synthetic series of commits in the upstream repo +where each commit: +1. Builds successfully. +2. Passes its own tests. +3. Introduces a small, coherent subset of the 1.0.0 changes. + +This modified history would be fed into the standard vendoring process. + +**Proposed commit series** (logical ordering): + +| # | Change Category | Scope | +|---|---|---| +| 1 | Type renames only (`EDGEWEIGHTS` → `EDGE_WEIGHTS`, etc.) | Mechanical | +| 2 | `FLAGS: no_rng` annotations | No behavioral change | +| 3 | Remove deprecated functions (one-by-one or grouped) | ~30 functions | +| 4 | `igraph_integer_t` → `igraph_int_t` | Alias exists, low risk | +| 5 | Attribute handler changes | Critical, manual | +| 6 | Parameter additions (weights, loops, etc.) | Function-by-function | +| 7 | Parameter type changes (BOOLEAN→LOOPS, BOOLEAN→EDGE_TYPE_SW) | Grouped | +| 8 | Function renames | Small batch | +| 9 | New functions | Additive only | +| 10 | Behavioral changes | Need careful testing | +| 11 | Interruption handler + RNG changes | R-specific | + +**Viability assessment with agentic coding tools**: + +This approach is **partially feasible** with current agentic tools, but has +significant challenges: + +#### What agents can do well: +- **Mechanical transformations**: Type renames, adding `FLAGS: no_rng`, updating + Stimulus YAML files—these are well-suited for agentic coding. +- **Test triage**: Running tests, categorizing failures, adding `skip()` markers. +- **Code generation**: Running `make -f Makefile-cigraph` and `cpp11::cpp_register()`. +- **Search and replace**: Updating old API names to new ones across R code. + +#### What agents struggle with: +- **Attribute handler rewrite**: This requires understanding the semantics of + igraph's attribute system deeply. The change from `igraph_vector_ptr_t` to + `igraph_attribute_record_list_t` and from `void*` to a typed union requires + careful manual coding with knowledge of R's SEXP types and igraph's internal + memory management. +- **Creating buildable intermediate commits in upstream**: This requires forking + the upstream repo, cherry-picking and modifying commits, resolving conflicts, + and ensuring each intermediate state compiles and passes C-level tests. + Current agents cannot effectively manage a multi-repository workflow with + complex git history manipulation. +- **Semantic parameter changes**: When a function gains a new `weights` parameter + that changes its position relative to other parameters, the R wrapper needs + to be updated with correct default values and argument ordering. This requires + understanding both the C semantics and the R user-facing API design. + +#### Practical assessment: +- Creating a fully rewritten history of ~760 commits where each builds and + passes tests is **not practical** even with agentic tools. The upstream + changes are too intertwined. +- A **manually curated series of 10–15 synthetic commits** (as outlined in the + table above) is feasible but would require ~1–2 weeks of expert work to + create, even with agentic assistance. +- The effort to create the rewritten history **exceeds** the effort of + Approach B (vendor + skip), making it a poor trade-off unless the rewritten + history can be reused for other language bindings (Python-igraph, etc.). + +**Verdict**: ⚠️ Theoretically sound but not cost-effective. The curated commit +approach could work for **future** major version upgrades if built into the +upstream release process. + +### 3.5 Approach E: Hybrid — Phased Vendoring with Preparatory PRs + +**Description**: Before vendoring 1.0.1, land a series of preparatory PRs on +the rigraph `main` branch that pre-adapt the R code for known breaking +changes—while still using C core 0.10.17. + +**Phase 1 — Preparatory PRs (on 0.10.17)**: +1. Update `types-RC.yaml` and `types-RR.yaml` to use new type names. Add + compatibility aliases in the YAML if needed, or update the upstream + `types.yaml` in the vendored copy. +2. Remove R wrappers for functions deprecated in 0.10.x that will be deleted + in 1.0.0 (e.g., `igraph_are_connected`, `igraph_tree`, `igraph_lattice`). + Since these already have replacement functions available, this is safe. +3. Update R tests to use new function names where the old ones are deprecated. +4. Pre-adapt the attribute handler code where possible (e.g., rename + `gettype` → `get_type`, prepare for union-based `value` field). + +**Phase 2 — Vendor 1.0.1**: +5. Vendor the C sources. +6. Fix remaining compilation errors (attribute handler, RNG, interrupt). +7. Skip failing tests. + +**Phase 3 — Fix Tests**: +8. Restore skipped tests one-by-one. + +**Pros**: Each preparatory PR is small, reviewable, and safe. The actual +vendoring step (Phase 2) is smaller because much of the R-side adaptation +is already done. + +**Cons**: Requires maintaining awareness of 1.0.0 changes while working on +0.10.17. Some changes can't be pre-adapted (e.g., functions that gain new +required parameters). + +**Verdict**: ✅ **Recommended as the most practical approach**. + +## 4. Recommended Strategy + +**Use Approach E (Hybrid) as the primary strategy, falling back to +Approach B for the actual vendoring step.** + +### Detailed Work Plan + +#### Phase 1: Preparatory PRs (each is a separate, small PR) + +**PR 1 — Type renames in Stimulus configs** +- Update `types-RC.yaml`: rename all old type names to new ones. +- Update `types-RR.yaml`: same. +- Since the vendored `types.yaml` still uses old names, add temporary aliases + or patch the vendored copy. +- Regenerate `rinterface.c` and `aaa-auto.R`. +- Run tests—should be no behavioral change. + +**PR 2 — Remove deprecated function wrappers** +- Identify R functions that wrap removed C functions. +- If the R function already delegates to the replacement, just update the + internal call. +- If the R function has no replacement wrapper yet, create one. +- Update tests accordingly. +- This can be split into multiple PRs by function category. + +**PR 3 — Pre-adapt attribute handler** +- Rename `gettype` → `get_type` in `rinterface_extra.c`. +- Where possible, start transitioning `void*` casts to the new union access + pattern (may require `#ifdef` guards or a compatibility shim). +- Test thoroughly. + +**PR 4 — Adapt for parameter changes where backward-compatible** +- For functions where parameters were added with defaults, update the R + wrappers to include the new parameters (passing defaults that reproduce + 0.10.x behavior). +- This makes the R API forward-compatible. + +#### Phase 2: Vendor 1.0.1 + +**PR 5 — Vendor C core 1.0.1** +- Run `vendor.sh` pointing at 1.0.1. +- Apply updated patches (some will need refreshing). +- Fix compilation errors in: + - `rinterface_extra.c` (attribute handler finalization) + - `rrandom.c` (RNG/interrupt changes) + - `rinterface.c` (regenerated, may need Stimulus YAML fixes) +- Run `R CMD build` to verify compilation. +- Run tests, skip all failures with category markers: + - `skip("C core 1.0.1: parameter change")` + - `skip("C core 1.0.1: behavioral change")` + - `skip("C core 1.0.1: removed function")` +- Create a tracking issue listing all skipped tests. + +#### Phase 3: Restore Tests + +**PRs 6–N — Fix skipped tests by category** +- Group test fixes by the type of breaking change. +- Each PR fixes one category and removes the corresponding `skip()` markers. +- Prioritize by user impact (e.g., shortest path functions before obscure + graph generators). + +## 5. Specific Technical Challenges + +### 5.1 The Attribute Handler Rewrite + +This is the highest-risk component. Key changes needed in `rinterface_extra.c`: + +``` +Old: igraph_vector_ptr_t *attr → igraph_attribute_record_t *rec = VECTOR(*attr)[i] +New: igraph_attribute_record_list_t *attr → igraph_attribute_record_t *rec = igraph_attribute_record_list_get_ptr(attr, i) +``` + +The `igraph_attribute_record_t.value` change from `void*` to a typed union +means changing patterns like: + +```c +// Old +igraph_vector_t *vec = (igraph_vector_t*) rec->value; + +// New +igraph_vector_t *vec = rec->value.numeric; // or .string, .boolean +``` + +There are ~111 references to `igraph_attribute` in `rinterface_extra.c`. +Approximately 30–40 of these need manual updates. + +### 5.2 Stimulus YAML Updates + +The `functions-R.yaml` file needs updates for: +- ~45 function signature changes (parameter additions, type changes, reordering) +- ~5 function renames +- ~15 removed functions (need to be removed or IGNOREd in R YAML) +- ~10 new functions (optionally added to R YAML) + +### 5.3 Patch Refreshing + +The 9 patches in `patch/` were written for 0.10.17 and may not apply cleanly +to 1.0.1. Each needs to be checked: + +| Patch | Risk | +|---|---| +| `0001-fix-include-quotes.patch` | Low—likely still applies | +| `0002-upstream-deps.patch` | Medium—bond/site percolation functions may have changed | +| `0003-Temporary-fix.patch` | Check if still needed | +| `0004-chore-Fix-remaining-Stimulus-types.patch` | May conflict with upstream type changes | +| `0005-Accept-out-parameter.patch` | Check if upstream resolved this | +| `0005-induced_subgraph_map.patch` | Function renamed to `_map`, check | +| `0006-count_reachable.patch` | Function finalized in 1.0.0, check defaults | +| `0101-Return-value-of-igraph_edges-*.patch` | `igraph_edges` changed, likely conflicts | +| `0102-Return-value-of-igraph_topological_sorting-*.patch` | Check | + +### 5.4 Snapshot Test Updates + +The `aaa-auto.md` snapshot file (230 KB) will need near-complete regeneration. +This is expected and manageable—run `testthat::snapshot_accept()` after +verifying the output is correct. + +## 6. Effort Estimates + +| Phase | Estimated Effort | Can Agent Assist? | +|---|---|---| +| Phase 1, PR 1 (type renames) | 2–4 hours | ✅ Fully automatable | +| Phase 1, PR 2 (deprecated fns) | 1–2 days | ✅ Mostly automatable | +| Phase 1, PR 3 (attribute handler prep) | 1–2 days | ⚠️ Needs expert review | +| Phase 1, PR 4 (parameter pre-adapt) | 1 day | ✅ Mostly automatable | +| Phase 2, PR 5 (vendor + compile) | 2–4 days | ⚠️ Attribute handler needs manual work | +| Phase 3, PRs 6–N (test fixes) | 1–3 weeks | ✅ Per-PR largely automatable | +| **Total** | **3–6 weeks** | | + +## 7. Recommendations + +1. **Start with Approach E** (preparatory PRs) to reduce the blast radius of + the vendoring step. + +2. **Prioritize the attribute handler** as the critical path. Consider + prototyping the rewrite before committing to the vendoring timeline. + +3. **Use agentic tools for mechanical changes** (type renames, YAML updates, + test triage) but have a human expert review the attribute handler and + RNG/interrupt changes. + +4. **Do not attempt to rewrite upstream history** unless the upstream igraph + team agrees to maintain such a history for future releases. The effort + exceeds the benefit for a one-time migration. + +5. **Consider coordinating with python-igraph** maintainers, who likely face + similar migration challenges. Shared learnings about the attribute handler + changes could save significant time. + +6. **Track progress with a GitHub project board** listing each skipped test + as an item. This makes the remaining work visible and parallelizable. + +## 8. Agentic History Rewrite: Detailed Feasibility + +The idea of creating a "rewritten history"—a series of commits between +0.10.17 and 1.0.1 where each commit builds and passes its own tests—is +appealing but faces several practical barriers: + +### 8.1 Why It's Hard + +1. **Entangled changes**: The 1.0.0 breaking changes were developed across + many feature branches (`feat/loop-edge-handling`, + `feat/rng-begin-end-removal`, `feat/pointer-attributes`, etc.) that touch + overlapping files. Disentangling them into a linear sequence of + independently-buildable commits requires resolving conflicts at each step. + +2. **Test coupling**: When a type like `EDGEWEIGHTS` is renamed to + `EDGE_WEIGHTS`, every test that uses this type must be updated + simultaneously. This creates large atomic change sets even for "simple" + renames. + +3. **C API interdependencies**: Some changes (like the attribute handler + overhaul) affect dozens of functions simultaneously. These cannot be + meaningfully subdivided. + +4. **Build system changes**: The C++14 requirement and other build system + changes affect the entire compilation unit. + +### 8.2 What Agents Could Do + +With current agentic capabilities (as of mid-2026): + +- **Create a plan** for the commit series: analyze the `functions.yaml` diff + and categorize changes—this document is essentially that plan. +- **Execute mechanical transformations**: apply type renames, add `no_rng` + flags, update YAML configs—agents excel at this. +- **Run builds and tests** at each step to verify the intermediate state + compiles and passes: agents can drive `R CMD check`. +- **Generate patches** for the vendored copy that implement individual + change categories. + +### 8.3 What Agents Cannot Do (Yet) + +- **Multi-repo git orchestration**: creating branches, cherry-picking, + resolving merge conflicts across the upstream igraph repo and rigraph + simultaneously. +- **Semantic C code rewriting**: understanding the attribute handler's + memory management and correctly rewriting it for the new API. +- **Judgment calls**: deciding which behavioral changes are bugs vs. + intentional, and how the R API should expose new parameters. + +### 8.4 Conclusion on History Rewrite + +A full history rewrite is **not viable** as a standalone approach. However, +elements of this idea can be incorporated into Approach E: + +- The preparatory PRs (Phase 1) are essentially the "rewritten history" + applied to rigraph rather than upstream. +- Each PR corresponds to one logical step in the migration. +- The difference is that we apply these changes to rigraph's code (R wrappers, + YAML configs, tests) rather than to the C library itself. + +This achieves the same goal—small, reviewable, independently-testable +steps—without the complexity of maintaining a synthetic upstream history.