Skip to content

feat: add a legacy executor version#1

Open
kp2pml30 wants to merge 21 commits into
v0.6-devfrom
feat/add-v0.2
Open

feat: add a legacy executor version#1
kp2pml30 wants to merge 21 commits into
v0.6-devfrom
feat/add-v0.2

Conversation

@kp2pml30

@kp2pml30 kp2pml30 commented Jun 30, 2026

Copy link
Copy Markdown
Member

Depends-On: https://github.com/genlayerlabs/genlayer-node/pull/1502
Depends-On: https://github.com/genlayerlabs/genlayer-dev-env/pull/95
Depends-On: https://github.com/genlayerlabs/genlayer-e2e/pull/619

Summary by CodeRabbit

  • New Features

    • Added support for an additional active version, expanding compatibility to more release lines.
    • Introduced new GitHub Actions automation for opening and linking executor PRs.
    • Added new CLI commands for creating branches, checking push readiness, and generating language bindings.
  • Bug Fixes

    • Improved compatibility handling for legacy executor runs.
    • Updated workflows to target the correct executor version automatically.
  • Chores

    • Reformatted and standardized several project configuration files.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c9d6a6d8-6271-47fd-b1c7-24d8721a60b7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds v0.2.x executor support alongside v0.3.x (submodule, monorepo config, compatibility rules, integration tests), introduces CI workflows/scripts to open and clean up cross-repo executor PRs, refactors genvm-tool's build configuration into a reusable ninja DSL plugin, adds a multi-language codegen subsystem and new git CLI commands, replaces genlayer_sdk re-exports in modules-interfaces with local types, and reformats various TOML/config files using a newly added Taplo formatter.

Changes

Multi-version executor support

Layer / File(s) Summary
Active versions and submodule registration
.genvm-monorepo-root, .gitmodules, executors/v0.2.x, executors/v0.3.x
Adds v0.2 to active-versions, registers the executors/v0.2.x submodule, and bumps both executor commit pointers.
Runner/executor compatibility policy
runners/compat.nix, flake.nix, implementation/default.nix
Implements a legacy v0.2 compatibility rule and reorganizes runner copying in the combine-genvm derivation; extends manager source paths for calldata crates.
Integration tests across active versions
tests/runner/genvm_tool_plugins/integration.py
Iterates active executor versions for integration test discovery, parameterizes case directories/reroute targets per version, and handles new leader nondeterminism kinds.

CI executor PR linking and cleanup

Layer / File(s) Summary
Open executor PR workflow and script
.github/workflows/branch_executor_prs.yaml, support/ci/open-executor-prs.py
New workflow triggers on manager PR open against v*-dev; script resolves the executor line, ensures the mirror branch exists, opens/reuses an executor PR, and upserts a linking comment.
Dynamic submodule resolution and cleanup
.github/workflows/branch_merge_into_dev.yaml, support/ci/genvm-merge-into-dev.py
Merge workflow resolves the executor submodule dynamically from PR base; merge script deletes the executor mirror branch post-merge.

genvm-tool DSL, codegen, and git commands

Layer / File(s) Summary
Shared ninja DSL plugin
tests/runner/genvm_tool_plugins/ninja.py
New plugin module providing RawStr/Build/Ninja DSL, glob/path helpers, and LineContext with per-line registry/install logic.
cmd_configure refactor
support/tools/genvm-tool/genvm_tool/cmd_configure.py
Removes the in-file ninja DSL, wires the file to the plugin module, adds per-line configurator hooks, and updates cargo/nix_eval/install wiring.
Codegen model and dispatch
support/tools/genvm-tool/genvm_tool/codegen/__init__.py, .../codegen/model.py
Adds the shared typed codegen model (Enum/Const/Consts/StrTrie, trie build/unfold/parse/load) and backend dispatch (render, generate).
Codegen language backends
.../codegen/rust.py, .../codegen/python.py, .../codegen/go.py, .../codegen/rst.py
Implements Rust, Python, Go, and RST renderers from the codegen model.
codegen CLI command
.../cmd_codegen.py, .../__main__.py
Adds the codegen CLI subcommand and registers it in TOPLEVEL.
Branch naming helpers
.../common.py
Adds release/branch-model regex and reworks Repo.line/feature_branch for pr/<line>/ namespacing.
git create-branches and check-for-push
.../git/create_branches.py, .../git/check_for_push.py, .../git/__init__.py
Adds interactive multi-repo branch creation and a read-only pre-push status survey command.
Tool packaging
.../default.nix, .../pyproject.toml
Adds questionary dependency.

modules-interfaces local types

Layer / File(s) Summary
Dependency changes
crates/modules-interfaces/Cargo.toml
Drops genlayer_sdk, adds bytes, reorders dependencies.
Local LLM types and codec
crates/modules-interfaces/src/lib.rs
Defines OutputFormat, prompt payload structs, and PromptTemplatePayload tagged enum with manual calldata Encode/Decode.
Local web types
crates/modules-interfaces/src/lib.rs
Defines RenderMode, WaitAfterLoaded (custom serde/calldata), RenderPayload, RequestMethod, Response, RequestPayload.

Formatting and tooling config

Layer / File(s) Summary
Taplo formatter introduction
.taplo.toml, .genvm-tool.py, support/nix/precommit/flake.nix
Adds Taplo config, a toml-format pre-commit hook, and the taplo package to precommit tools.
Cargo/pyproject/ruff reformatting
implementation/Cargo.toml, docs/website/pyproject.toml, ruff.toml, support/tools/git-third-party/ruff.toml
Reformats dependency/section ordering without changing effective values.

Sequence Diagram(s)

sequenceDiagram
  participant ManagerPR
  participant OpenExecutorPRsScript
  participant ExecutorRepo
  ManagerPR->>OpenExecutorPRsScript: pull_request_target opened (v*-dev base)
  OpenExecutorPRsScript->>OpenExecutorPRsScript: line_of(base)
  OpenExecutorPRsScript->>ExecutorRepo: executor_branch_exists(branch)
  OpenExecutorPRsScript->>ExecutorRepo: open_pr or existing_pr
  ExecutorRepo-->>OpenExecutorPRsScript: executor PR URL
  OpenExecutorPRsScript->>ManagerPR: upsert_comment(executor URL)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

A rabbit hops through branches new,
v0.2 joins v0.3's crew 🐇
Ninja files now share one den,
codegen sprouts in rust, py, go, and rst again,
taplo combs the toml's fur so neat —
thump thump, this warren's review is sweet! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: introducing legacy v0.2 executor support alongside the existing executor line.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-v0.2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@kp2pml30 kp2pml30 changed the base branch from v0.6 to v0.6-dev June 30, 2026 19:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/branch_executor_prs.yaml:
- Around line 15-17: The pull_request_target trigger in this workflow only fires
on opened, so the executor mirror PR and linked comment can become stale or
missing on later updates. Update the workflow trigger to also include
synchronize, reopened, and edited so the executor PR refreshes automatically
when new commits arrive, the PR is reopened, or the base branch changes; keep
this change in the pull_request_target event block.

In `@crates/modules-interfaces/src/lib.rs`:
- Around line 623-655: The WaitAfterLoaded deserialization currently uses
deserialize_str, so visit_none in WaitAfterLoadedVisitor is never reached and
null still fails even though it maps to Millis(0). Update
WaitAfterLoaded::deserialize to use deserialize_option or another deserializer
that can pass null through to the visitor, and keep the null-to-Millis(0)
behavior in WaitAfterLoadedVisitor; otherwise, if null should not be accepted,
remove the visit_none handling and related expectation.

In `@support/ci/open-executor-prs.py`:
- Around line 165-180: The executor branch naming in main is still keyed only by
line and HEAD_REF, which can collide across PRs sharing the same branch name.
Update the branch key to include a stable PR identifier, then use that same
PR-scoped key consistently in executor_branch_exists, existing_pr/open_pr, and
the cleanup logic in support/ci/genvm-merge-into-dev.py so the mirror ref is
unique per PR and cannot be reused or deleted across different PRs.

In `@support/tools/genvm-tool/genvm_tool/codegen/python.py`:
- Around line 16-19: The Python codegen string emission is using raw text, so
_pydump() must be changed to emit properly escaped Python string literals
instead of wrapping values in simple quotes. Update _pydump() to handle embedded
quotes, backslashes, and newlines safely, and revise _emit_param() so it does
not place raw trie/path text directly inside an inner f-string where braces can
be interpreted as formatting syntax.

In `@support/tools/genvm-tool/genvm_tool/codegen/rust.py`:
- Around line 14-17: The `_dump()` helper is escaping Python strings with
`json.dumps()` in a way that can emit `\uXXXX` sequences instead of valid Rust
string literals. Update `_dump()` in `rust.py` to use a Rust-aware string
escaper (or disable ASCII escaping with `json.dumps(..., ensure_ascii=False)`)
so enum values, consts, and trie literals preserve Unicode safely in generated
Rust code.

In `@support/tools/genvm-tool/genvm_tool/common.py`:
- Around line 107-109: Tighten the _MODEL_BRANCH regex in common.py so it only
matches the documented branch shapes used by Repo.line(), not arbitrary dotted
versions. Update the pattern in _MODEL_BRANCH to accept only main, version
branches like v<major>.<minor>.x, and dev branches like v<major>.<minor>-dev, so
malformed names such as v0.3.1-dev do not bypass feature namespacing.
- Around line 143-158: The feature_branch() logic is only treating names
prefixed with pr/{line}/ as already namespaced, so re-running create-branches
can double-prefix branches that are already in the pr/... form. Update
feature_branch() to recognize any already-namespaced executor branch (for
example any name matching the pr/<line>/... pattern, not just the current line)
and return it unchanged, while still preserving the existing model-branch
passthrough behavior.

In `@support/tools/genvm-tool/genvm_tool/git/create_branches.py`:
- Around line 56-60: The branch scanning logic in create_branches.py is treating
branches with no upstream as ahead=0, which causes fresh local branches with
unpushed commits to be skipped. Update the candidate detection in the branch
enumeration flow (the code using _git(..., 'rev-list', '--count',
'@{upstream}..HEAD')) so that a missing upstream still marks the branch as a
candidate if it has local commits, rather than defaulting to zero. Keep the
existing rev-list count for branches with an upstream, but add a separate
fallback path for branches without one.

In `@tests/runner/genvm_tool_plugins/integration.py`:
- Around line 860-869: The integration test discovery in the CASES_DIR setup can
silently yield zero tests when the per-version suite path is missing or
misconfigured. In the integration runner logic around EXEC_SUBDIR and CASES_DIR,
add an explicit existence and non-empty check before calling CASES_DIR.glob(),
and fail immediately with a clear error if the expected suite directory for the
selected executor_version is absent or contains no .jsonnet cases. Use the
existing CASES_DIR and reroute_to flow to anchor the check so each active
executor line cannot pass with zero collected tests.

In `@tests/runner/genvm_tool_plugins/ninja.py`:
- Around line 158-190: The build edge generated by Ninja in finish() does not
ensure parent directories exist for nested outputs, so clean builds can fail
when outputs like out/executor/... are written. Update the Ninja DSL emission
around finish() (and the related output-producing rules it feeds) to add a
generic directory-creation step for each output’s parent path before cp, touch,
or redirection writes happen, so nested outputs always have their directories
created first.
- Around line 295-297: The crate file collection in the Ninja generator is
incorrectly assuming every crate has a local Cargo.lock, which breaks executor
crates that rely on the workspace lockfile. Update the logic in the
register_cargo path that builds all_files so it only adds Cargo.lock when the
crate actually owns one, or otherwise points to the workspace lockfile for
executor crates; use the existing glob/base handling to keep Cargo.toml and
source files unchanged.
🪄 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: 15d7d002-109e-4cbb-9fdb-40c83debab38

📥 Commits

Reviewing files that changed from the base of the PR and between 0274116 and 6a72970.

⛔ Files ignored due to path filters (1)
  • implementation/Cargo.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (37)
  • .genvm-monorepo-root
  • .genvm-tool.py
  • .github/workflows/branch_executor_prs.yaml
  • .github/workflows/branch_merge_into_dev.yaml
  • .gitmodules
  • .taplo.toml
  • crates/modules-interfaces/Cargo.toml
  • crates/modules-interfaces/src/lib.rs
  • docs/website/pyproject.toml
  • executors/v0.2.x
  • executors/v0.3.x
  • flake.nix
  • implementation/Cargo.toml
  • implementation/default.nix
  • ruff.toml
  • runners/compat.nix
  • support/ci/genvm-merge-into-dev.py
  • support/ci/open-executor-prs.py
  • support/nix/precommit/flake.nix
  • support/tools/genvm-tool/default.nix
  • support/tools/genvm-tool/genvm_tool/__main__.py
  • support/tools/genvm-tool/genvm_tool/cmd_codegen.py
  • support/tools/genvm-tool/genvm_tool/cmd_configure.py
  • support/tools/genvm-tool/genvm_tool/codegen/__init__.py
  • support/tools/genvm-tool/genvm_tool/codegen/go.py
  • support/tools/genvm-tool/genvm_tool/codegen/model.py
  • support/tools/genvm-tool/genvm_tool/codegen/python.py
  • support/tools/genvm-tool/genvm_tool/codegen/rst.py
  • support/tools/genvm-tool/genvm_tool/codegen/rust.py
  • support/tools/genvm-tool/genvm_tool/common.py
  • support/tools/genvm-tool/genvm_tool/git/__init__.py
  • support/tools/genvm-tool/genvm_tool/git/check_for_push.py
  • support/tools/genvm-tool/genvm_tool/git/create_branches.py
  • support/tools/genvm-tool/pyproject.toml
  • support/tools/git-third-party/ruff.toml
  • tests/runner/genvm_tool_plugins/integration.py
  • tests/runner/genvm_tool_plugins/ninja.py

Comment on lines +15 to +17
pull_request_target:
branches: ['v*-dev']
types: [opened]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files:\n'
git ls-files .github/workflows/branch_executor_prs.yaml support/ci/open-executor-prs.py

printf '\nWorkflow outline:\n'
ast-grep outline .github/workflows/branch_executor_prs.yaml --view expanded || true

printf '\nScript outline:\n'
ast-grep outline support/ci/open-executor-prs.py --view expanded || true

printf '\nWorkflow contents:\n'
cat -n .github/workflows/branch_executor_prs.yaml

printf '\nScript contents (selected):\n'
wc -l support/ci/open-executor-prs.py
sed -n '1,240p' support/ci/open-executor-prs.py

Repository: genlayerlabs/genvm-manager

Length of output: 7810


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Refs to branch_executor_prs or open-executor-prs:\n'
rg -n "branch_executor_prs|open-executor-prs|genvm-executor-prs|reopen the PR|push the executor submodule branch" . --glob '!**/.git/**'

printf '\nWorkflows with pull_request_target / reopened / synchronize / edited:\n'
rg -n "pull_request_target:|types:|reopened|synchronize|edited|workflow_dispatch" .github/workflows --glob '*.yml' --glob '*.yaml'

Repository: genlayerlabs/genvm-manager

Length of output: 3000


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Refs to branch_executor_prs or open-executor-prs:\n'
rg -n "branch_executor_prs|open-executor-prs|genvm-executor-prs|reopen the PR|push the executor submodule branch" . --glob '!**/.git/**'

printf '\nWorkflows with pull_request_target / reopened / synchronize / edited / workflow_dispatch:\n'
rg -n "pull_request_target:|types:|reopened|synchronize|edited|workflow_dispatch" .github/workflows --glob '*.yml' --glob '*.yaml'

Repository: genlayerlabs/genvm-manager

Length of output: 3020


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'branch_retarget.yaml:\n'
cat -n .github/workflows/branch_retarget.yaml

printf '\nsupport/ci/genvm-merge-into-dev.py (selected):\n'
wc -l support/ci/genvm-merge-into-dev.py
sed -n '1,260p' support/ci/genvm-merge-into-dev.py

Repository: genlayerlabs/genvm-manager

Length of output: 11775


🌐 Web query:

GitHub Actions pull_request_target events edited reopened synchronize base branch change PR retarget triggers documentation

💡 Result:

The pull_request_target event in GitHub Actions triggers workflows based on specific activity types on a pull request. By default, it runs only on the following activity types: opened, synchronize, and reopened [1][2]. To respond to other activity types, such as edited, you must explicitly specify them using the types keyword in your workflow configuration [1][2]. Regarding your specific scenarios: 1. Edited: The edited activity type is not included in the default trigger set. If you want a workflow to run when a pull request is edited (which includes changing the target/base branch), you must add edited to your types list [3][4]. 2. Reopened: This is included in the default trigger set [1][2]. 3. Synchronize: This is included in the default trigger set [1][2]. 4. Base branch change (Retargeting): Changing the base branch of a pull request triggers a pull request event with the action type edited [3][4]. Therefore, to trigger a pull_request_target workflow upon a base branch change, you must explicitly include the edited activity type [3][4]. Example configuration for these scenarios: on: pull_request_target: types: [opened, synchronize, reopened, edited] When a pull request's base branch is changed, the event payload will contain an action of edited and a changes object that reflects the modification to the base property [3]. Ensure that you handle the security implications of using pull_request_target, as workflows triggered by this event run with elevated permissions (the base repository's GITHUB_TOKEN) and execute using the code from the repository's base branch rather than the pull request head [5][6].

Citations:


Add retry/update triggers here

pull_request_target is limited to opened here, so the workflow won’t rerun when the executor mirror branch appears later, when the PR is reopened, or when the base branch changes (edited). Add synchronize, reopened, and edited so the linked executor PR/comment can refresh automatically instead of staying missing or stale.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 14-17: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely

(dangerous-triggers)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/branch_executor_prs.yaml around lines 15 - 17, The
pull_request_target trigger in this workflow only fires on opened, so the
executor mirror PR and linked comment can become stale or missing on later
updates. Update the workflow trigger to also include synchronize, reopened, and
edited so the executor PR refreshes automatically when new commits arrive, the
PR is reopened, or the base branch changes; keep this change in the
pull_request_target event block.

Comment on lines +623 to +655
fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(WaitAfterLoaded::Millis(0))
}

fn visit_str<E>(self, value: &str) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
if let Some(ms_str) = value.strip_suffix("ms") {
let millis = ms_str.parse::<u64>().map_err(E::custom)?;
Ok(WaitAfterLoaded::Millis(millis))
} else if let Some(secs_str) = value.strip_suffix("s") {
let seconds = secs_str.parse::<u64>().map_err(E::custom)?;
Ok(WaitAfterLoaded::Seconds(seconds))
} else {
Err(E::invalid_value(
serde::de::Unexpected::Str(value),
&"expected a string ending with 's' or 'ms'",
))
}
}
}

impl<'de> serde::Deserialize<'de> for WaitAfterLoaded {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(WaitAfterLoadedVisitor)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant area with line numbers
sed -n '590,690p' crates/modules-interfaces/src/lib.rs

# Search for the type and any other serde helpers around it
rg -n "WaitAfterLoaded|deserialize_option|deserialize_str|visit_none|visit_unit|visit_some" crates/modules-interfaces/src/lib.rs

Repository: genlayerlabs/genvm-manager

Length of output: 4762


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the surrounding struct and any serde annotations on the field
sed -n '710,760p' crates/modules-interfaces/src/lib.rs

# Find any tests or docs that mention null / default / wait_after_loaded
rg -n "wait_after_loaded|null|WaitAfterLoadedVisitor|Millis\(0\)|Seconds\(" crates/modules-interfaces/src/lib.rs crates/modules-interfaces -g '!target'

Repository: genlayerlabs/genvm-manager

Length of output: 5603


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find call sites / external-facing uses of the payload type
rg -n "RenderPayload|wait_after_loaded" crates modules -g '!**/target/**'

# Inspect the custom serde / JSON handling nearby to see whether null is a real input form
sed -n '80,170p' crates/modules-interfaces/src/lib.rs
sed -n '920,960p' crates/modules-interfaces/src/lib.rs

Repository: genlayerlabs/genvm-manager

Length of output: 821


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '80,170p' crates/modules-interfaces/src/lib.rs
sed -n '920,960p' crates/modules-interfaces/src/lib.rs

rg -n "RenderPayload|WaitAfterLoaded|default_none|deserialize_option|serde_json::Value::Null|push_null|visit_null|visit_some|visit_unit" crates/modules-interfaces/src/lib.rs

Repository: genlayerlabs/genvm-manager

Length of output: 6176


Deserialize through deserialize_option if null should map to Millis(0). visit_none never fires under deserialize_str, so null is still rejected before the visitor can handle it; otherwise remove the null expectation from the deserializer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/modules-interfaces/src/lib.rs` around lines 623 - 655, The
WaitAfterLoaded deserialization currently uses deserialize_str, so visit_none in
WaitAfterLoadedVisitor is never reached and null still fails even though it maps
to Millis(0). Update WaitAfterLoaded::deserialize to use deserialize_option or
another deserializer that can pass null through to the visitor, and keep the
null-to-Millis(0) behavior in WaitAfterLoadedVisitor; otherwise, if null should
not be accepted, remove the visit_none handling and related expectation.

Comment thread support/ci/open-executor-prs.py Outdated
Comment on lines +165 to +180
def main() -> None:
line = line_of(BASE_REF)
if not line:
print(f'base `{BASE_REF}` is not a v<X>-dev branch; nothing to do')
return
head = f'pr/{line}/{HEAD_REF}'
base = BASE_REF # the executor's dev branch carries the same name
if not executor_branch_exists(head):
print(
f'executor branch `{head}` is not on {EXECUTOR_REPO} yet; nothing to open '
f'(push the executor submodule branch first, then reopen the PR)'
)
return
url = existing_pr(head, base) or open_pr(head, base)
print(f'executor PR: {url}')
upsert_comment([f'executor: {url}'])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== open-executor-prs.py ==\n'
sed -n '1,260p' support/ci/open-executor-prs.py

printf '\n== genvm-merge-into-dev.py ==\n'
sed -n '1,260p' support/ci/genvm-merge-into-dev.py

printf '\n== search for HEAD_REF / branch key usage ==\n'
rg -n "HEAD_REF|headRefName|pr/.*HEAD_REF|line_of\(|executor branch|existing_pr|open_pr|delete.*branch|branch key" support/ci

Repository: genlayerlabs/genvm-manager

Length of output: 16094


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,260p' support/ci/open-executor-prs.py
sed -n '1,260p' support/ci/genvm-merge-into-dev.py
rg -n "HEAD_REF|headRefName|pr/.*HEAD_REF|line_of\(|executor branch|existing_pr|open_pr|delete.*branch|branch key" support/ci

Repository: genlayerlabs/genvm-manager

Length of output: 15989


🏁 Script executed:

#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path

for path in [Path('support/ci/open-executor-prs.py'), Path('support/ci/genvm-merge-into-dev.py')]:
    print(f"\n== {path} ==")
    text = path.read_text()
    for needle in ['HEAD_REF', 'headRefName', 'pr/', 'existing_pr', 'open_pr', 'delete', 'branch']:
        if needle in text:
            print(f'contains {needle}')
PY

Repository: genlayerlabs/genvm-manager

Length of output: 412


Make the executor mirror key PR-scoped. pr/<line>/<HEAD_REF> is still ambiguous across PRs that use the same branch name, so the wrong executor PR can be reused and support/ci/genvm-merge-into-dev.py can delete a shared mirror ref. Include a stable PR identifier in the branch name and use the same key in cleanup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@support/ci/open-executor-prs.py` around lines 165 - 180, The executor branch
naming in main is still keyed only by line and HEAD_REF, which can collide
across PRs sharing the same branch name. Update the branch key to include a
stable PR identifier, then use that same PR-scoped key consistently in
executor_branch_exists, existing_pr/open_pr, and the cleanup logic in
support/ci/genvm-merge-into-dev.py so the mirror ref is unique per PR and cannot
be reused or deleted across different PRs.

Comment on lines +16 to +19
def _pydump(v) -> str:
if isinstance(v, str):
return f"'{v}'"
return str(v)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import ast

samples = {
    "raw_single_quote": "X = 'can't'\n",
    "raw_fstring_braces": "def f(v):\n    return f'prefix {bad} {v}'\n",
}
for name, src in samples.items():
    try:
        ast.parse(src)
        print(name, "OK")
    except SyntaxError as e:
        print(name, "FAIL:", e.msg)
PY

# Optional: scan repository JSON files for characters that would exercise the bug.
fd -e json . | xargs -r rg -n "[\'\\\\{}]"

Repository: genlayerlabs/genvm-manager

Length of output: 338


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and nearby helpers.
wc -l support/tools/genvm-tool/genvm_tool/codegen/python.py
ast-grep outline support/tools/genvm-tool/genvm_tool/codegen/python.py --view expanded

# Read the relevant ranges with line numbers.
sed -n '1,220p' support/tools/genvm-tool/genvm_tool/codegen/python.py | cat -n

Repository: genlayerlabs/genvm-manager

Length of output: 5383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Reproduce the suspected generation patterns with small Python probes.
python3 - <<'PY'
import json

def _pydump(v):
    if isinstance(v, str):
        return f"'{v}'"
    return str(v)

samples = ["can't", r"a\b", "line1\nline2", "{x}", "plain"]
for s in samples:
    print("INPUT:", repr(s))
    print("OUT  :", _pydump(s))
    print("JSON :", json.dumps(s, ensure_ascii=False))
    print()
PY

Repository: genlayerlabs/genvm-manager

Length of output: 404


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect trie/source model to see what characters can reach the generators.
ast-grep outline support/tools/genvm-tool/genvm_tool/codegen/model.py --view expanded
sed -n '1,260p' support/tools/genvm-tool/genvm_tool/codegen/model.py | cat -n

# Search for any explicit escaping/normalization of const, enum, or trie values.
rg -n "escape|quote|repr|json.dumps|parts|values|StrTrie|TrieNode" support/tools/genvm-tool/genvm_tool/codegen

Repository: genlayerlabs/genvm-manager

Length of output: 17679


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the trie/model definitions and any upstream normalization for emitted strings.
ast-grep outline support/tools/genvm-tool/genvm_tool/codegen/model.py --view expanded
sed -n '1,260p' support/tools/genvm-tool/genvm_tool/codegen/model.py | cat -n

rg -n "escape|quote|repr|json.dumps|parts|values|StrTrie|TrieNode|split|parse" support/tools/genvm-tool/genvm_tool/codegen

Repository: genlayerlabs/genvm-manager

Length of output: 18208


Escape Python string emission at all codegen sites

  • _pydump() needs real Python escaping; wrapping raw text in '...' breaks on ', \, and newlines.
  • _emit_param() also needs to avoid nesting raw trie text inside the inner f-string, since { / } in path text can be treated as f-string syntax.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@support/tools/genvm-tool/genvm_tool/codegen/python.py` around lines 16 - 19,
The Python codegen string emission is using raw text, so _pydump() must be
changed to emit properly escaped Python string literals instead of wrapping
values in simple quotes. Update _pydump() to handle embedded quotes,
backslashes, and newlines safely, and revise _emit_param() so it does not place
raw trie/path text directly inside an inner f-string where braces can be
interpreted as formatting syntax.

Comment on lines +14 to +17
def _dump(v) -> str:
if isinstance(v, str):
return json.dumps(v)
return str(v)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import json
print("json.dumps('é') ->", json.dumps("é"))
print("If your codegen JSON can contain non-ASCII, the current Rust output is affected.")
PY

# Optional: scan repository JSON files for non-ASCII inputs that would exercise this path.
fd -e json . | xargs -r rg -nP '[^\x00-\x7F]'

Repository: genlayerlabs/genvm-manager

Length of output: 934


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== rust.py outline ==\n'
ast-grep outline support/tools/genvm-tool/genvm_tool/codegen/rust.py --view expanded || true

printf '\n== relevant slice of rust.py ==\n'
sed -n '1,220p' support/tools/genvm-tool/genvm_tool/codegen/rust.py

printf '\n== _dump usages ==\n'
rg -n "_dump\(" support/tools/genvm-tool/genvm_tool/codegen/rust.py support/tools/genvm-tool/genvm_tool -g '!**/__pycache__/**' || true

Repository: genlayerlabs/genvm-manager

Length of output: 9908


Use a Rust-safe string escaper in _dump(). _dump() is shared by enum values, consts, and trie literals, and json.dumps()’s default ASCII escaping turns non-ASCII text into "\uXXXX", which is not valid Rust string syntax. Switching to a Rust-aware escaper (or json.dumps(..., ensure_ascii=False)) avoids breaking generated code for Unicode strings.

Proposed fix
 def _dump(v) -> str:
 	if isinstance(v, str):
-		return json.dumps(v)
+		return json.dumps(v, ensure_ascii=False)
 	return str(v)
📝 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
def _dump(v) -> str:
if isinstance(v, str):
return json.dumps(v)
return str(v)
def _dump(v) -> str:
if isinstance(v, str):
return json.dumps(v, ensure_ascii=False)
return str(v)
🧰 Tools
🪛 ast-grep (0.44.0)

[info] 15-15: use jsonify instead of json.dumps for JSON output
Context: json.dumps(v)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@support/tools/genvm-tool/genvm_tool/codegen/rust.py` around lines 14 - 17,
The `_dump()` helper is escaping Python strings with `json.dumps()` in a way
that can emit `\uXXXX` sequences instead of valid Rust string literals. Update
`_dump()` in `rust.py` to use a Rust-aware string escaper (or disable ASCII
escaping with `json.dumps(..., ensure_ascii=False)`) so enum values, consts, and
trie literals preserve Unicode safely in generated Rust code.

Comment on lines +143 to +158
def feature_branch(self, name: str) -> str:
"""Physical branch name for the logical feature branch `name` in this repo.

The manager is its own repo, so it carries `name` verbatim. Every executor
submodule, however, shares ONE remote (`genvm-executor`): a bare `name`
would collide between two active lines that branch off it at once. So an
executor feature branch is namespaced under `pr/<line>/` (e.g.
`pr/v0.3/<name>`) — the `<line>` segment is what keeps the two lines
distinct, matching how the release branches (`v0.3-dev`, `v0.3.x`) are
already line-scoped and thus never clash. Release-model branches and an
already-prefixed `name` pass through unchanged (idempotent).
"""
line = self.line
if line is None or name.startswith(f'pr/{line}/') or _MODEL_BRANCH.fullmatch(name):
return name
return f'pr/{line}/{name}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make already-namespaced executor branches fully idempotent.

This only skips prefixing when the name already starts with pr/{line}/. If the manager branch is already something like pr/v0.3/foo, re-running create-branches for executors/v0.2.x turns it into pr/v0.2/pr/v0.3/foo, even though the docstring says already-prefixed names pass through unchanged.

Suggested fix
 	def feature_branch(self, name: str) -> str:
@@
 		line = self.line
-		if line is None or name.startswith(f'pr/{line}/') or _MODEL_BRANCH.fullmatch(name):
+		if line is None or _MODEL_BRANCH.fullmatch(name):
+			return name
+		if re.match(r'pr/v\d+\.\d+/', name):
 			return name
 		return f'pr/{line}/{name}'
📝 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
def feature_branch(self, name: str) -> str:
"""Physical branch name for the logical feature branch `name` in this repo.
The manager is its own repo, so it carries `name` verbatim. Every executor
submodule, however, shares ONE remote (`genvm-executor`): a bare `name`
would collide between two active lines that branch off it at once. So an
executor feature branch is namespaced under `pr/<line>/` (e.g.
`pr/v0.3/<name>`) — the `<line>` segment is what keeps the two lines
distinct, matching how the release branches (`v0.3-dev`, `v0.3.x`) are
already line-scoped and thus never clash. Release-model branches and an
already-prefixed `name` pass through unchanged (idempotent).
"""
line = self.line
if line is None or name.startswith(f'pr/{line}/') or _MODEL_BRANCH.fullmatch(name):
return name
return f'pr/{line}/{name}'
def feature_branch(self, name: str) -> str:
"""Physical branch name for the logical feature branch `name` in this repo.
The manager is its own repo, so it carries `name` verbatim. Every executor
submodule, however, shares ONE remote (`genvm-executor`): a bare `name`
would collide between two active lines that branch off it at once. So an
executor feature branch is namespaced under `pr/<line>/` (e.g.
`pr/v0.3/<name>`) — the `<line>` segment is what keeps the two lines
distinct, matching how the release branches (`v0.3-dev`, `v0.3.x`) are
already line-scoped and thus never clash. Release-model branches and an
already-prefixed `name` pass through unchanged (idempotent).
"""
line = self.line
if line is None or _MODEL_BRANCH.fullmatch(name):
return name
if re.match(r'pr/v\d+\.\d+/', name):
return name
return f'pr/{line}/{name}'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@support/tools/genvm-tool/genvm_tool/common.py` around lines 143 - 158, The
feature_branch() logic is only treating names prefixed with pr/{line}/ as
already namespaced, so re-running create-branches can double-prefix branches
that are already in the pr/... form. Update feature_branch() to recognize any
already-namespaced executor branch (for example any name matching the
pr/<line>/... pattern, not just the current line) and return it unchanged, while
still preserving the existing model-branch passthrough behavior.

Comment on lines +56 to +60
# `@{upstream}` errors out when the branch has no upstream; treat that as 0.
ahead = 0
r = _git(repo, 'rev-list', '--count', '@{upstream}..HEAD')
if r.returncode == 0 and r.stdout.strip():
ahead = int(r.stdout.strip())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don't drop unpushed branches just because they have no upstream yet.

A clean local branch with commits but no configured upstream currently looks like ahead = 0, so it never enters the candidate list. That's exactly the kind of unpushed work this command is supposed to preserve on a fresh branch.

Suggested fix
-	# `@{upstream}` errors out when the branch has no upstream; treat that as 0.
 	ahead = 0
 	r = _git(repo, 'rev-list', '--count', '@{upstream}..HEAD')
 	if r.returncode == 0 and r.stdout.strip():
 		ahead = int(r.stdout.strip())
+	else:
+		# No upstream yet: count commits not reachable from any remote-tracking ref.
+		r = _git(repo, 'rev-list', '--count', '--not', '--remotes', 'HEAD')
+		if r.returncode == 0 and r.stdout.strip():
+			ahead = int(r.stdout.strip())
📝 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
# `@{upstream}` errors out when the branch has no upstream; treat that as 0.
ahead = 0
r = _git(repo, 'rev-list', '--count', '@{upstream}..HEAD')
if r.returncode == 0 and r.stdout.strip():
ahead = int(r.stdout.strip())
ahead = 0
r = _git(repo, 'rev-list', '--count', '@{upstream}..HEAD')
if r.returncode == 0 and r.stdout.strip():
ahead = int(r.stdout.strip())
else:
# No upstream yet: count commits not reachable from any remote-tracking ref.
r = _git(repo, 'rev-list', '--count', '--not', '--remotes', 'HEAD')
if r.returncode == 0 and r.stdout.strip():
ahead = int(r.stdout.strip())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@support/tools/genvm-tool/genvm_tool/git/create_branches.py` around lines 56 -
60, The branch scanning logic in create_branches.py is treating branches with no
upstream as ahead=0, which causes fresh local branches with unpushed commits to
be skipped. Update the candidate detection in the branch enumeration flow (the
code using _git(..., 'rev-list', '--count', '@{upstream}..HEAD')) so that a
missing upstream still marks the branch as a candidate if it has local commits,
rather than defaulting to zero. Keep the existing rev-list count for branches
with an upstream, but add a separate fallback path for branches without one.

Comment on lines +860 to 869
EXEC_SUBDIR = os.environ.get(
'GENVM_TEST_EXEC_SUBDIR', f'executors/{executor_version}.x'
)
CASES_DIR = local_ctx.shared.root_dir.joinpath(EXEC_SUBDIR, 'tests', 'integration')

# Run this line's cases against this line's own executor. The version key
# (e.g. "v0.2") maps to the concrete built version (e.g. "v0.2.16").
reroute_to = build_info['executor_versions'].get(executor_version, executor_version)

jsonnet_files = list(CASES_DIR.glob('**/*.jsonnet'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fail fast when a per-version integration suite is missing.

CASES_DIR.glob() silently returns no files if the executor checkout or override path is wrong, so an active executor line can pass with zero collected tests. Add an explicit directory/non-empty check before collection.

Proposed fix
 	CASES_DIR = local_ctx.shared.root_dir.joinpath(EXEC_SUBDIR, 'tests', 'integration')
+	if not CASES_DIR.is_dir():
+		raise FileNotFoundError(
+			f'integration cases directory for {executor_version} does not exist: {CASES_DIR}'
+		)
 
 	# Run this line's cases against this line's own executor. The version key
 	# (e.g. "v0.2") maps to the concrete built version (e.g. "v0.2.16").
 	reroute_to = build_info['executor_versions'].get(executor_version, executor_version)
 
-	jsonnet_files = list(CASES_DIR.glob('**/*.jsonnet'))
-	jsonnet_files.sort()
+	jsonnet_files = sorted(CASES_DIR.glob('**/*.jsonnet'))
+	if not jsonnet_files:
+		raise RuntimeError(f'no integration jsonnet files found for {executor_version}: {CASES_DIR}')
📝 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
EXEC_SUBDIR = os.environ.get(
'GENVM_TEST_EXEC_SUBDIR', f'executors/{executor_version}.x'
)
CASES_DIR = local_ctx.shared.root_dir.joinpath(EXEC_SUBDIR, 'tests', 'integration')
# Run this line's cases against this line's own executor. The version key
# (e.g. "v0.2") maps to the concrete built version (e.g. "v0.2.16").
reroute_to = build_info['executor_versions'].get(executor_version, executor_version)
jsonnet_files = list(CASES_DIR.glob('**/*.jsonnet'))
EXEC_SUBDIR = os.environ.get(
'GENVM_TEST_EXEC_SUBDIR', f'executors/{executor_version}.x'
)
CASES_DIR = local_ctx.shared.root_dir.joinpath(EXEC_SUBDIR, 'tests', 'integration')
if not CASES_DIR.is_dir():
raise FileNotFoundError(
f'integration cases directory for {executor_version} does not exist: {CASES_DIR}'
)
# Run this line's cases against this line's own executor. The version key
# (e.g. "v0.2") maps to the concrete built version (e.g. "v0.2.16").
reroute_to = build_info['executor_versions'].get(executor_version, executor_version)
jsonnet_files = sorted(CASES_DIR.glob('**/*.jsonnet'))
if not jsonnet_files:
raise RuntimeError(f'no integration jsonnet files found for {executor_version}: {CASES_DIR}')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/runner/genvm_tool_plugins/integration.py` around lines 860 - 869, The
integration test discovery in the CASES_DIR setup can silently yield zero tests
when the per-version suite path is missing or misconfigured. In the integration
runner logic around EXEC_SUBDIR and CASES_DIR, add an explicit existence and
non-empty check before calling CASES_DIR.glob(), and fail immediately with a
clear error if the expected suite directory for the selected executor_version is
absent or contains no .jsonnet cases. Use the existing CASES_DIR and reroute_to
flow to anchor the check so each active executor line cannot pass with zero
collected tests.

Comment on lines +158 to +190
def finish(self) -> None:
assert self.outputs, 'build edge must have at least one output'
n = self.ninja
n.buf.append('build')
for o in self.outputs:
n.buf.append(' ')
n._scalar(o)
if self.implicit_outputs:
n.buf.append(' |')
for o in self.implicit_outputs:
n.buf.append(' ')
n._scalar(o)
n.buf.append(': ')
n.buf.append(self.rule)
for d in self.deps:
n.buf.append(' ')
n._scalar(d)
if self.implicit_deps:
n.buf.append(' |')
for d in self.implicit_deps:
n.buf.append(' ')
n._scalar(d)
if self.order_only_deps:
n.buf.append(' ||')
for d in self.order_only_deps:
n.buf.append(' ')
n._scalar(d)
n.buf.append('\n')
for key, value in self.props.items():
n.buf.append(f' {key} = ')
n._value(value)
n.buf.append('\n')
n.buf.append('\n')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Create parent directories for nested outputs.

The DSL emits outputs like out/executor/<real>/bin/genvm and out/executor/<real>/data/*.json, but no generic path creates those parents. On a clean build, cp, touch, or > can fail before writing the output.

Proposed fix
 class Build:
@@
 	def finish(self) -> None:
 		assert self.outputs, 'build edge must have at least one output'
 		n = self.ninja
+		for output in [*self.outputs, *self.implicit_outputs]:
+			n._ensure_output_parent(output)
 		n.buf.append('build')
@@
 class Ninja:
@@
 	def _resolve_path(self, value: Path) -> str:
@@
 		return str(value)
+
+	def _output_path(self, value) -> Path | None:
+		if isinstance(value, RawStr):
+			return None
+		if isinstance(value, Path):
+			return value if value.is_absolute() else self.source_dir / value
+		text = str(value)
+		if not text or '$' in text:
+			return None
+		p = Path(text)
+		return p if p.is_absolute() else self.build_dir / p
+
+	def _ensure_output_parent(self, value) -> None:
+		path = self._output_path(value)
+		if path is not None:
+			path.parent.mkdir(parents=True, exist_ok=True)

Also applies to: 267-275, 347-347, 437-438, 452-457

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/runner/genvm_tool_plugins/ninja.py` around lines 158 - 190, The build
edge generated by Ninja in finish() does not ensure parent directories exist for
nested outputs, so clean builds can fail when outputs like out/executor/... are
written. Update the Ninja DSL emission around finish() (and the related
output-producing rules it feeds) to add a generic directory-creation step for
each output’s parent path before cp, touch, or redirection writes happen, so
nested outputs always have their directories created first.

Comment on lines +295 to +297
all_files = [crate_dir / p.relative_to(base) for p in glob(base, '**/*.rs')]
all_files += [crate_dir / 'Cargo.toml', crate_dir / 'Cargo.lock']

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import json
from pathlib import Path

root = Path(".")
cfg = json.loads((root / ".genvm-monorepo-root").read_text())
paths = ["implementation", "crates/modules-interfaces"]

for key in cfg["active-versions"]:
    exec_rel = f"executors/{key}.x"
    paths.extend([
        f"{exec_rel}/executor",
        f"{exec_rel}/executor/crates/common",
        f"{exec_rel}/executor/crates/sdk-rs",
    ])

for rel in paths:
    lock = root / rel / "Cargo.lock"
    print(f"{rel}: {'ok' if lock.exists() else 'missing Cargo.lock'}")
PY

Repository: genlayerlabs/genvm-manager

Length of output: 545


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant implementation and call sites.
ast-grep outline tests/runner/genvm_tool_plugins/ninja.py --view expanded
printf '\n---\n'
sed -n '250,330p' tests/runner/genvm_tool_plugins/ninja.py
printf '\n---\n'
rg -n "register_cargo\(" -S tests/runner/genvm_tool_plugins

Repository: genlayerlabs/genvm-manager

Length of output: 5860


Don't require a per-crate Cargo.lock here
register_cargo() always adds <crate>/Cargo.lock as an implicit dependency, but the executor crates under executors/v0.2.x/executor and executors/v0.3.x/executor don't have local lockfiles. That makes the Ninja edge fail on a missing input; use the workspace lockfile or skip this dependency when the crate doesn't own one.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/runner/genvm_tool_plugins/ninja.py` around lines 295 - 297, The crate
file collection in the Ninja generator is incorrectly assuming every crate has a
local Cargo.lock, which breaks executor crates that rely on the workspace
lockfile. Update the logic in the register_cargo path that builds all_files so
it only adds Cargo.lock when the crate actually owns one, or otherwise points to
the workspace lockfile for executor crates; use the existing glob/base handling
to keep Cargo.toml and source files unchanged.

kp2pml30 added 6 commits July 1, 2026 13:46
Add executors/v0.2.x as a second genvm-executor submodule (branch v0.2.x) and
activate it in .genvm-monorepo-root. Its runners are the prebuilt v0.2.16
universal tarball. compat.nix freezes the v0.2 line so its legacy-ABI runners
serve only v0.2 executors and do not roll up into v0.3.
@kp2pml30

kp2pml30 commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

GenVM PR actions

Tick a box to run it (the box unticks itself when handled). Actions only run while the PR has the ci-safe label.

  • Force run full tests
  • Rerun full tests
  • Merge into dev

Full GenVM CI runs only when rtm or run-full-tests is set — "Force run full tests" is a sticky toggle for run-full-tests. Adding rtm marks the PR ready-to-merge and also runs full tests. Merge requires: rtm, green full tests, green E2E, and the branch 0 commits behind.

kp2pml30 added 14 commits July 1, 2026 15:29
…ners 🐛

The v0.2.x config points at executor/<version>/legacy-runners, but the
packaging still pooled every line's runners into the shared top-level
runners/ dir, so that folder was never populated. Split the runner list by
line: forward-rolling lines keep the shared runners/ tree, while legacy
(v0.2.x) runners are laid out at executor/<version>/legacy-runners/ and
overlaid onto the combined `genvm` distribution and the dev out/ tree.
Two ways a hook inherited state from the invoking dev shell / parent repo and
ran against the wrong thing:

- A pre-commit / commit-msg hook runs with GIT_DIR / GIT_INDEX_FILE / ... set
  to the parent repo. `hook run` fans out to every repo (and its submodules)
  by explicit path, so those pinned each `git -C <submodule>` to the parent's
  index/objects and crashed them (`git diff --cached` -> exit 128, e.g.
  ".git/index: Not a directory" since a submodule .git is a gitlink file).
  Strip the repo-scoping GIT_* variables up front.

- The dev shell exports PYTHONPATH pointing at its own genvm-tool build, so the
  hook's python imported genvm_tool from there instead of the freshly installed
  tool (running stale code). The stub now clears PYTHONPATH / NIX_PYTHONPATH so
  it always uses its own baked-in deps.
Nix filters the flake source to tracked files only, dropping submodule
contents; the flake eval forces executors/<line>.x/manifest.json, so
every nix develop/build .# blew up during evaluation. Also check out
submodules in module-test-python, which cds into the executor tree.
Old-format ABI calldata stored the contract method name under the key
"method"; the current ABI uses the empty key "". Detect old-format
calldata at the run endpoint, log an error so stale callers are visible,
and rewrite the key in place before handing it to the executor.
…aries 🐛

nixpkgs pins lief 0.17.0, whose ELF Builder counts NOBITS sections in
its file-layout calc and emits a corrupt section-header table on our
~290 MB musl PIE binaries, breaking patchelf finalize. Override to
0.17.6, which skips NOBITS in layout. See lief-project/LIEF#1315.
The rust and rust-fuzz pipeline scripts invoked a non-existent
support/tools/genvm-tool/genvm-tool wrapper, failing with exit 127.
These scripts run inside the nix rust-test dev shell, which already
provides genvm-tool on PATH (packages-0), so call it bare.

Claude-Session: https://claude.ai/code/session_01Sa2F8Kv4XdrPPNgnBhbWrS
The Google Cloud auth step is only needed to upload freshly-built runners
to the GCS cache, which is already best-effort (`|| true` in
test-rust.sh); downloads use the public bucket and need no auth. On fork
PRs (or when GCP_SA_KEY is unset) google-github-actions/auth errors out
and fails the whole job. Gate the auth and Cloud SDK steps on the secret
being present so tests still run without caching.

Claude-Session: https://claude.ai/code/session_01Sa2F8Kv4XdrPPNgnBhbWrS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant