From ec74bc4ec4dea4ed58947ba3ebc845073dbb5865 Mon Sep 17 00:00:00 2001 From: Kelly Guo Date: Wed, 27 May 2026 18:46:37 -0700 Subject: [PATCH] Merges changes from main --- .dockerignore | 2 +- .github/workflows/README.md | 7 + .github/workflows/docs.yaml | 1 + .github/workflows/nightly-changelog.yml | 190 ++++++++++++++---- source/isaaclab/isaaclab/utils/string.py | 8 +- tools/template/templates/agents/skrl_amp_cfg | 13 +- tools/template/templates/agents/skrl_ippo_cfg | 4 +- .../template/templates/agents/skrl_mappo_cfg | 6 +- 8 files changed, 182 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/README.md diff --git a/.dockerignore b/.dockerignore index 51d4ec61a457..b718bd108a8a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -23,7 +23,7 @@ recordings/ **/__pycache__/ **/*.egg-info/ # ignore isaac sim symlink -_isaac_sim? +_isaac_sim # Docker history docker/.isaac-lab-docker-history # ignore uv environment diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 000000000000..0e38d5fdd3c8 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,7 @@ +# CI Workflows + +`schedule:` and `workflow_dispatch:` triggers fire **only from the default +branch (`main`)**. A workflow YAML must live on `main` for its cron to +register — the same file on other branches has no effect. `pull_request:` +and `push:` triggers fire from the event branch's file and work normally +on `develop`. diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 013be3a5b126..1d3c0496e1c2 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -132,3 +132,4 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs/_build keep_files: false + force_orphan: true diff --git a/.github/workflows/nightly-changelog.yml b/.github/workflows/nightly-changelog.yml index 7e55ad7450c6..839130ffccec 100644 --- a/.github/workflows/nightly-changelog.yml +++ b/.github/workflows/nightly-changelog.yml @@ -6,19 +6,38 @@ # Nightly auto-compile: rolls accumulated fragments under # ``source//changelog.d/`` into per-package ``CHANGELOG.rst`` entries, # bumps each ``extension.toml``, deletes consumed fragments, and pushes the -# result back to ``develop``. Keeps the develop branch's changelog current -# without requiring a maintainer to run ``compile`` by hand. +# result back to each branch in the configured list. Keeps every tracked +# branch's changelog current without requiring a maintainer to run +# ``compile`` by hand. # -# The push uses ``CHANGELOG_PAT`` (a personal access token / fine-grained -# GitHub App token with ``contents:write`` on this repo) when it's -# available so downstream CI runs on the auto-commit. Falls back to -# ``GITHUB_TOKEN`` — sufficient for the push itself, but pushes signed -# with ``GITHUB_TOKEN`` do not trigger workflow runs on the resulting -# commit, which is by design (avoids infinite loops) but means the -# Docker / docs rebuild won't re-trigger off the nightly's auto-commit. +# Scheduled workflow — must live on the default branch (``main``) for the +# cron to register. See ``.github/workflows/README.md``. +# +# Adding a branch to the nightly set is a one-line edit to ``env.CRON_BRANCHES`` +# below. Each target branch uses its own ``tools/changelog/cli.py`` (the +# same copy the PR gate already runs), so the nightly compile honours the +# same rules that validated the fragments. +# +# The push uses a short-lived GitHub App installation token minted from +# ``CHANGELOG_APP_ID`` + ``CHANGELOG_APP_PRIVATE_KEY`` (repo secrets). The +# App must be installed on this repository with ``contents: write`` and +# added to the bypass-actor list of each target branch's ruleset so the +# auto-commit can push directly without satisfying required-checks / +# required-approval gates. +# Commits signed by an App token (unlike ``GITHUB_TOKEN``) are treated as +# external pushes, so they DO trigger downstream workflow runs (Docker +# rebuild, docs, etc.) without needing a separate PAT. name: Nightly Changelog Compilation +# Branches the nightly cron compiles. Single source of truth — append a +# ref here to extend the nightly set (the active release branch belongs +# here). Each branch must carry ``tools/changelog/cli.py`` and the +# isaaclab-bot App must be in its branch-ruleset bypass list. +# Surrounding whitespace per entry is stripped by the resolver below. +env: + CRON_BRANCHES: develop,release/3.0.0-beta2 + on: schedule: # Run nightly at 5 AM UTC (one hour after daily-compatibility, so we @@ -26,44 +45,130 @@ on: - cron: '0 5 * * *' workflow_dispatch: inputs: + branch: + # Manual trigger is always a single branch. Free-text on purpose + # — scales to any branch (e.g. a new ``release/*`` cut from + # develop) without needing to update the workflow. A branch + # that lacks ``tools/changelog/cli.py`` fails the verify step + # below with a clear error, which is the desired failure mode. + description: 'Branch to compile (e.g. develop, release/3.0.0-beta2). Must carry tools/changelog/cli.py.' + required: true + type: string dry_run: description: 'Preview only — do not commit / push' required: false type: boolean default: false -concurrency: - # Only one nightly compile may be in flight at a time. ``cancel-in-progress`` - # is intentionally false: if a previous run is still finishing its push, we - # queue rather than abort it mid-commit. - group: nightly-changelog - cancel-in-progress: false - permissions: - contents: write + # Reduced: the App installation token below carries its own write scope. + # GITHUB_TOKEN only needs read access for the standard checkout machinery. + contents: read jobs: + resolve-branches: + # CSV → JSON array bridge. ``workflow_dispatch`` inputs can only be + # string / bool / choice, so the branch list arrives as a comma- + # separated string; the matrix below needs a JSON list to fan out. + # Mirrors the ``setup-versions`` job in ``daily-compatibility.yml``. + name: Resolve branch list + runs-on: ubuntu-latest + timeout-minutes: 1 + outputs: + branches: ${{ steps.b.outputs.branches }} + steps: + - id: b + env: + # Schedule → the CRON_BRANCHES list. Manual → the single branch + # the maintainer entered. The two paths are intentionally + # asymmetric: cron is the configured set, manual is exactly one + # branch (required input). + BRANCHES: ${{ github.event_name == 'schedule' && env.CRON_BRANCHES || inputs.branch }} + # ``EVENT_NAME`` mirrors ``github.event_name`` so the guard below + # branches on it without re-interpolating into the shell. + EVENT_NAME: ${{ github.event_name }} + run: | + # CSV → JSON array, trimming surrounding whitespace per entry. + # Manual produces a 1-element array; cron produces N elements. + arr=$(echo "$BRANCHES" | tr ',' '\n' | xargs -n1 | jq -R . | jq -s -c .) + # Manual trigger contract: exactly one branch. A maintainer who + # pastes a comma-separated list into the dispatch form should + # see a clear error, not a silent multi-branch fan-out. + if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$(echo "$arr" | jq 'length')" -ne 1 ]; then + echo "::error::Manual trigger accepts exactly one branch; got $arr. Fire the workflow separately per branch." + exit 1 + fi + echo "branches=$arr" >> "$GITHUB_OUTPUT" + echo "Resolved branches: $arr" + compile-changelog: - name: Compile changelog fragments + name: Compile changelog fragments (${{ matrix.branch }}) + needs: resolve-branches runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + # Independent branches: one failing shouldn't cancel the others. + # Each matrix entry shows up as a separate job tile in the Actions + # UI, so ``release/3.0.0-beta2`` failing doesn't hide ``develop``'s + # success (and ``gh run rerun --failed`` re-runs only the failed + # entry). Mirrors ``daily-compatibility.yml``'s matrix style. + fail-fast: false + matrix: + branch: ${{ fromJson(needs.resolve-branches.outputs.branches) }} + concurrency: + # Per-branch group: two runs against the same ref queue, but + # different refs (develop and a release branch) compile in parallel. + group: nightly-changelog-${{ matrix.branch }} + cancel-in-progress: false steps: + # Mint a short-lived (1 h) installation access token for the + # ``isaaclab-bot`` GitHub App. The App is on each target branch's + # ruleset bypass list, so its push lands without needing the + # standard required-checks / required-approval pipeline. + - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + id: app-token + with: + # ``client-id`` (the App's OAuth client ID, ``Iv23...``) supersedes + # the deprecated ``app-id`` integer input as of v3.x. + client-id: ${{ secrets.CHANGELOG_APP_CLIENT_ID }} + private-key: ${{ secrets.CHANGELOG_APP_PRIVATE_KEY }} + # Declare the scope the App must have so token mint fails loudly + # if the App is misconfigured, instead of failing silently at the + # push step. + permission-contents: write + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - # Operate on develop, not the repo's default branch. Scheduled - # workflows fire from the default branch's workflow file by - # default, but we want the *checkout* to be develop so the - # compile sees develop's accumulated fragments and the push - # writes back to develop. - ref: develop - # Use a PAT so the auto-commit triggers downstream CI; falls back - # to GITHUB_TOKEN which is sufficient for the push itself. - token: ${{ secrets.CHANGELOG_PAT || secrets.GITHUB_TOKEN }} + # Operate on the target branch, not the repo's default branch. + # Scheduled workflows fire from the default branch's workflow + # file, but the *checkout* is the branch we're compiling so the + # compile sees that branch's accumulated fragments and the push + # writes back to it. + ref: ${{ matrix.branch }} + # App token (vs. GITHUB_TOKEN) means the push is signed by + # ``isaaclab-bot`` — the bypass identity — and downstream CI + # workflows DO trigger on the resulting commit. + token: ${{ steps.app-token.outputs.token }} # Full history so the compiler can resolve each fragment's merge # time via ``git log --diff-filter=A --first-parent``. fetch-depth: 0 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + - name: Verify changelog tooling exists on target branch + env: + TARGET_BRANCH: ${{ matrix.branch }} + run: | + # Loud-fail this matrix entry (not the whole run — ``fail-fast: + # false`` keeps siblings going) when the target branch lacks + # ``tools/changelog/cli.py``. A red tile is the desired signal: + # a branch in the nightly set without the compile tooling is a + # configuration error, not a "no-op" condition. + if [ ! -f tools/changelog/cli.py ]; then + echo "::error::Branch '$TARGET_BRANCH' is missing tools/changelog/cli.py — drop it from env.CRON_BRANCHES (or the dispatch input) or restore the tooling on that branch." + exit 1 + fi + + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" @@ -77,10 +182,20 @@ jobs: python3 tools/changelog/cli.py compile $ARGS - name: Commit and push if fragments were compiled - if: inputs.dry_run != 'true' + if: ${{ !inputs.dry_run }} + env: + # Pass the matrix branch through an env var rather than + # interpolating ``${{ matrix.branch }}`` directly into ``run:``. + # The interpolation happens *before* shell quoting, so an + # adversarial input could escape the surrounding quotes; the + # env passthrough keeps the value inside the shell's variable + # space where standard quoting protects it. + TARGET_BRANCH: ${{ matrix.branch }} run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + # Author commits as the App's bot user so the GitHub UI attributes + # them correctly. ID 282401363 is isaaclab-bot[bot]'s user ID. + git config user.name "isaaclab-bot[bot]" + git config user.email "282401363+isaaclab-bot[bot]@users.noreply.github.com" git add source/*/changelog.d/ \ source/*/docs/CHANGELOG.rst \ source/*/config/extension.toml @@ -113,7 +228,14 @@ jobs: done } > "$MSG_FILE" git commit -F "$MSG_FILE" - # Push explicitly to develop so we don't accidentally write - # to the source ref of a workflow_dispatch run. - git push origin HEAD:develop + # Rebase onto the target branch's current tip in case a human + # commit landed during this run (~2 min window between + # checkout and push). Without this the push fails non-fast- + # forward and the batch waits for the next run. ``refs/heads/`` + # is explicit so a same-named tag (if one ever exists) can't + # disambiguate the wrong way. + git pull --rebase origin "refs/heads/$TARGET_BRANCH" + # Push explicitly to the target branch so we don't accidentally + # write to the source ref of a workflow_dispatch run. + git push origin "HEAD:refs/heads/$TARGET_BRANCH" fi diff --git a/source/isaaclab/isaaclab/utils/string.py b/source/isaaclab/isaaclab/utils/string.py index 4e7790006ade..0625ee4eca6c 100644 --- a/source/isaaclab/isaaclab/utils/string.py +++ b/source/isaaclab/isaaclab/utils/string.py @@ -333,11 +333,11 @@ def resolve_matching_names( When a list of query regular expressions is provided, the function checks each target string against each query regular expression and returns the indices of the matched strings and the matched strings. - If the :attr:`preserve_order` is True, the ordering of the matched indices and names is the same as the order + If the :attr:`preserve_order` is False, the ordering of the matched indices and names is the same as the order of the provided list of strings. This means that the ordering is dictated by the order of the target strings and not the order of the query regular expressions. - If the :attr:`preserve_order` is False, the ordering of the matched indices and names is the same as the order + If the :attr:`preserve_order` is True, the ordering of the matched indices and names is the same as the order of the provided list of query regular expressions. For example, consider the list of strings is ['a', 'b', 'c', 'd', 'e'] and the regular expressions are ['a|c', 'b']. @@ -393,11 +393,11 @@ def resolve_matching_names_values( use it during initialization only (e.g. action/actuator config resolution), so caching would add complexity without a measurable benefit. - If the :attr:`preserve_order` is True, the ordering of the matched indices and names is the same as the order + If the :attr:`preserve_order` is False, the ordering of the matched indices and names is the same as the order of the provided list of strings. This means that the ordering is dictated by the order of the target strings and not the order of the query regular expressions. - If the :attr:`preserve_order` is False, the ordering of the matched indices and names is the same as the order + If the :attr:`preserve_order` is True, the ordering of the matched indices and names is the same as the order of the provided list of query regular expressions. For example, consider the dictionary is {"a|d|e": 1, "b|c": 2}, the list of strings is ['a', 'b', 'c', 'd', 'e']. diff --git a/tools/template/templates/agents/skrl_amp_cfg b/tools/template/templates/agents/skrl_amp_cfg index 0946e4c6e6fa..be95d6447ade 100644 --- a/tools/template/templates/agents/skrl_amp_cfg +++ b/tools/template/templates/agents/skrl_amp_cfg @@ -70,12 +70,14 @@ agent: learning_rate: 5.0e-05 learning_rate_scheduler: null learning_rate_scheduler_kwargs: null - state_preprocessor: RunningStandardScaler + observation_preprocessor: RunningStandardScaler + observation_preprocessor_kwargs: null + state_preprocessor: null state_preprocessor_kwargs: null value_preprocessor: RunningStandardScaler value_preprocessor_kwargs: null - amp_state_preprocessor: RunningStandardScaler - amp_state_preprocessor_kwargs: null + amp_observation_preprocessor: RunningStandardScaler + amp_observation_preprocessor_kwargs: null random_timesteps: 0 learning_starts: 0 grad_norm_clip: 0.0 @@ -86,10 +88,9 @@ agent: value_loss_scale: 2.5 discriminator_loss_scale: 5.0 amp_batch_size: 512 - task_reward_weight: 0.0 - style_reward_weight: 1.0 + task_reward_scale: 0.0 + style_reward_scale: 2.0 discriminator_batch_size: 4096 - discriminator_reward_scale: 2.0 discriminator_logit_regularization_scale: 0.05 discriminator_gradient_penalty_scale: 5.0 discriminator_weight_decay_scale: 1.0e-04 diff --git a/tools/template/templates/agents/skrl_ippo_cfg b/tools/template/templates/agents/skrl_ippo_cfg index a89939f95543..b9a699bb9616 100644 --- a/tools/template/templates/agents/skrl_ippo_cfg +++ b/tools/template/templates/agents/skrl_ippo_cfg @@ -49,7 +49,9 @@ agent: learning_rate_scheduler: KLAdaptiveLR learning_rate_scheduler_kwargs: kl_threshold: 0.008 - state_preprocessor: RunningStandardScaler + observation_preprocessor: RunningStandardScaler + observation_preprocessor_kwargs: null + state_preprocessor: null state_preprocessor_kwargs: null value_preprocessor: RunningStandardScaler value_preprocessor_kwargs: null diff --git a/tools/template/templates/agents/skrl_mappo_cfg b/tools/template/templates/agents/skrl_mappo_cfg index 255b30eac810..968c1a9ba5df 100644 --- a/tools/template/templates/agents/skrl_mappo_cfg +++ b/tools/template/templates/agents/skrl_mappo_cfg @@ -23,7 +23,7 @@ models: clip_actions: False network: - name: net - input: OBSERVATIONS + input: STATES layers: [32, 32] activations: elu output: ONE @@ -49,10 +49,10 @@ agent: learning_rate_scheduler: KLAdaptiveLR learning_rate_scheduler_kwargs: kl_threshold: 0.008 + observation_preprocessor: RunningStandardScaler + observation_preprocessor_kwargs: null state_preprocessor: RunningStandardScaler state_preprocessor_kwargs: null - shared_state_preprocessor: RunningStandardScaler - shared_state_preprocessor_kwargs: null value_preprocessor: RunningStandardScaler value_preprocessor_kwargs: null random_timesteps: 0