feat: add a legacy executor version#1
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis 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. ChangesMulti-version executor support
CI executor PR linking and cleanup
genvm-tool DSL, codegen, and git commands
modules-interfaces local types
Formatting and tooling config
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
implementation/Cargo.lockis 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.tomlcrates/modules-interfaces/Cargo.tomlcrates/modules-interfaces/src/lib.rsdocs/website/pyproject.tomlexecutors/v0.2.xexecutors/v0.3.xflake.niximplementation/Cargo.tomlimplementation/default.nixruff.tomlrunners/compat.nixsupport/ci/genvm-merge-into-dev.pysupport/ci/open-executor-prs.pysupport/nix/precommit/flake.nixsupport/tools/genvm-tool/default.nixsupport/tools/genvm-tool/genvm_tool/__main__.pysupport/tools/genvm-tool/genvm_tool/cmd_codegen.pysupport/tools/genvm-tool/genvm_tool/cmd_configure.pysupport/tools/genvm-tool/genvm_tool/codegen/__init__.pysupport/tools/genvm-tool/genvm_tool/codegen/go.pysupport/tools/genvm-tool/genvm_tool/codegen/model.pysupport/tools/genvm-tool/genvm_tool/codegen/python.pysupport/tools/genvm-tool/genvm_tool/codegen/rst.pysupport/tools/genvm-tool/genvm_tool/codegen/rust.pysupport/tools/genvm-tool/genvm_tool/common.pysupport/tools/genvm-tool/genvm_tool/git/__init__.pysupport/tools/genvm-tool/genvm_tool/git/check_for_push.pysupport/tools/genvm-tool/genvm_tool/git/create_branches.pysupport/tools/genvm-tool/pyproject.tomlsupport/tools/git-third-party/ruff.tomltests/runner/genvm_tool_plugins/integration.pytests/runner/genvm_tool_plugins/ninja.py
| pull_request_target: | ||
| branches: ['v*-dev'] | ||
| types: [opened] |
There was a problem hiding this comment.
🎯 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.pyRepository: 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.pyRepository: 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:
- 1: https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows
- 2: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- 3: https://github.com/scratchfoundation/understand-gha-triggers
- 4: https://stackoverflow.com/questions/77495124/trigger-a-github-workflow-on-target-branch-changed
- 5: https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target
- 6: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
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.
| 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) | ||
| } |
There was a problem hiding this comment.
🎯 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.rsRepository: 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.rsRepository: 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.rsRepository: 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.
| 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}']) |
There was a problem hiding this comment.
🗄️ 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/ciRepository: 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/ciRepository: 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}')
PYRepository: 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.
| def _pydump(v) -> str: | ||
| if isinstance(v, str): | ||
| return f"'{v}'" | ||
| return str(v) |
There was a problem hiding this comment.
🎯 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 -nRepository: 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()
PYRepository: 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/codegenRepository: 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/codegenRepository: 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.
| def _dump(v) -> str: | ||
| if isinstance(v, str): | ||
| return json.dumps(v) | ||
| return str(v) |
There was a problem hiding this comment.
🎯 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__/**' || trueRepository: 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.
| 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.
| 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}' |
There was a problem hiding this comment.
🎯 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.
| 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.
| # `@{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()) |
There was a problem hiding this comment.
🎯 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.
| # `@{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.
| 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')) |
There was a problem hiding this comment.
🎯 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.
| 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.
| 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') |
There was a problem hiding this comment.
🩺 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.
| all_files = [crate_dir / p.relative_to(base) for p in glob(base, '**/*.rs')] | ||
| all_files += [crate_dir / 'Cargo.toml', crate_dir / 'Cargo.lock'] | ||
|
|
There was a problem hiding this comment.
🩺 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'}")
PYRepository: 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_pluginsRepository: 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.
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.
GenVM PR actionsTick a box to run it (the box unticks itself when handled). Actions only run while the PR has the
Full GenVM CI runs only when |
…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
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
Bug Fixes
Chores