Skip to content

fix(onboard): prune inactive messaging presets for OpenClaw - #10490

Open
hunglp6d wants to merge 30 commits into
mainfrom
fix/10153-openclaw-messaging-presets
Open

fix(onboard): prune inactive messaging presets for OpenClaw#10490
hunglp6d wants to merge 30 commits into
mainfrom
fix/10153-openclaw-messaging-presets

Conversation

@hunglp6d

@hunglp6d hunglp6d commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Outcome

OpenClaw and Hermes rebuilds now derive messaging policy and credential-provider authority from the canonical active-channel selection. An inactive configured channel no longer keeps its built-in egress or provider binding, while active channels and same-named custom policy entries remain intact.

Reason

The existing inactive-preset guard did not cover every OpenClaw selection path. Rebuild also filtered only channels marked disabled, so a configured channel with active: false could retain policy and provider authority. This left policy entries that referenced providers the rebuilt sandbox could not use.

Related issues

Refs #10153

Changes

  • Apply the shared inactive messaging-preset guard to each agent supported by the channel manifests.
  • Read rebuild agent state from the messaging plan instead of the nullable registry field.
  • Use the canonical active-channel selector for rebuild policy deltas and credential-provider authority.
  • Remove inactive built-in policy keys while preserving same-named custom entries.
  • Cover OpenClaw and Hermes resume, rebuild, inactive-channel, active-channel, custom-ownership, missing-state, and WeChat credential-binding behavior.
  • Remove the unused credential-binding fixture helper and share the policy-application test setup.

Verification

  • Twelve focused suites — 267 tests passed after integration with the current WeChat messaging behavior.
  • CLI type-checking and repository checks — passed.
  • Focused Oxlint, Oxfmt, and diff checks — passed.
  • Normal commit hooks — passed.
  • Independent security review — eight categories passed; security testing remains pending the candidate Slack pairing qualification.
  • Independent documentation review — passed; no public documentation change is required.
  • The diff contains no secret, API key, or credential.

Review notes

The final change affects 11 files with 396 additions and 156 deletions. The 552 lines of churn are flagged for focused review.

Sensitive paths include onboarding, policy selection, messaging, and sandbox rebuild. The change narrows authority and does not create a product surface or restore durable NemoClaw policy state.

PR #10359 carried the first attempt at the same OpenClaw gap. This PR preserves its contributor attribution and contains the in-repository branch needed for candidate messaging qualification.

The required openclaw-slack-pairing candidate E2E remains outstanding. Do not approve or merge until that run passes for the published revision and every repository gate passes.


Signed-off-by: Ho Lim subhoya@gmail.com
Signed-off-by: Hung Le hple@nvidia.com
Signed-off-by: Apurv Kumaria akumaria@nvidia.com

#10281 stopped Hermes from carrying a repository messaging preset for a channel
the sandbox does not configure. OpenClaw reaches the same selection paths, so it
kept applying a preset for an unconfigured channel, including the Discord preset
that names a sandbox-scoped credential provider create-plan validation rejects.

Resolve the agent set from supportedAgents in the channel manifests instead of
naming Hermes in the guard, and rename the helper to match what it now covers.
An agent that appears in no manifest keeps its selection untouched, a missing
messaging plan stays no authority to call a channel inactive, and a custom preset
that shadows the name is still preserved.

Read the rebuild agent from the messaging plan rather than the registry entry.
getSandboxAgentRegistryFields stores agent as null for OpenClaw, so the registry
field made every default OpenClaw rebuild skip the guard.

Build rebuild's enabled-channel set with getActiveChannelIdsFromPlan. Filtering on
`disabled` alone counted a configured channel the plan cannot start, which kept its
egress preset and left the same dangling credential binding behind.

Give the rebuild fixtures production-shaped plans. The existing custom-policy
fixture passes even when the shared helper is a no-op, and the lifecycle fixture
omitted `active` and its credential hashes, so it read as fully inactive once
rebuild resolved the canonical active set.

This supersedes #10359, which reported the OpenClaw gap from a fork branch where
live E2E cannot reach the credentialed targets.

Refs #10153

Co-authored-by: Ho Lim <subhoya@gmail.com>
Signed-off-by: Hung Le <hple@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f70f25c0-ae56-4bea-b42a-81871d5eb85f

📥 Commits

Reviewing files that changed from the base of the PR and between 1b8094a and 579adea.

📒 Files selected for processing (4)
  • src/lib/actions/sandbox/rebuild-backup-phase.test.ts
  • src/lib/onboard/policy-selection-application.test.ts
  • test/helpers/messaging-plan-fixtures.test.ts
  • test/helpers/messaging-plan-fixtures.ts
💤 Files with no reviewable changes (1)
  • test/helpers/messaging-plan-fixtures.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/onboard/policy-selection-application.test.ts

Included review availability: Your plan provides up to 12 included reviews per hour; 10 remain after this review.


📝 Walkthrough

Walkthrough

Messaging preset pruning now supports Hermes and OpenClaw. Onboarding reconciliation and sandbox rebuild flows use typed messaging plans, active channel IDs, and credential availability checks.

Changes

Messaging preset pruning

Layer / File(s) Summary
General messaging preset pruning
src/lib/onboard/messaging-policy-presets.ts, src/lib/onboard/messaging-policy-presets.test.ts
The pruning helper derives supported agents from built-in channel manifests. It preserves selections for unsupported agents or missing messaging plans. Tests cover Hermes, OpenClaw, and custom preset ownership.
Onboarding reconciliation integration
src/lib/onboard/policy-preset-reconciliation.ts, src/lib/onboard/policy-selection.ts, src/lib/onboard/policy-resume-selection.test.ts, src/lib/onboard/policy-selection-application.test.ts, src/lib/onboard/openclaw-otel-policy-presets.test.ts
Onboarding reconciliation and selection use the generalized pruner. Tests cover stale preset removal, enabled-channel filtering, and custom preset retention for Hermes and OpenClaw.
Shared messaging plan fixtures
test/helpers/messaging-plan-fixtures.ts, test/helpers/messaging-plan-fixtures.test.ts
Fixtures derive channel activity from disabled channels and generate deterministic credential bindings. Channel override support and its isolation test were removed.
Sandbox rebuild integration and validation
src/lib/actions/sandbox/rebuild-backup-phase.ts, src/lib/actions/sandbox/rebuild-backup-phase.test.ts, src/lib/actions/sandbox/rebuild-flow-lifecycle.test.ts
Sandbox rebuilds use typed messaging plans and active channel IDs. Tests cover inactive Discord presets, missing bot-token credentials, and disabled Slack channels.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 579ad

The change consistently prunes inactive messaging presets for OpenClaw across onboarding, selection, and rebuild flows. No actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: brandonpelfrey

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 12 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the primary change: pruning inactive messaging presets for OpenClaw during onboarding.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/10153-openclaw-messaging-presets

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

@github-code-quality

github-code-quality Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall line coverage in commit 64591d4 in the fix/10153-openclaw-m... branch remains at 96%, unchanged from commit b6cbeb2 in the main branch.

TypeScript / code-coverage/cli

The overall line coverage in commit 64591d4 in the fix/10153-openclaw-m... branch remains at 84%, unchanged from commit b6cbeb2 in the main branch.

Show a line coverage summary of the most impacted files.
File main b6cbeb2 fix/10153-openclaw-m... 64591d4 +/-
src/lib/policy/tiers.ts 96% 90% -6%
src/lib/onboard...rchestration.ts 43% 42% -1%
src/lib/onboard...uild-context.ts 74% 74% 0%
src/lib/onboard...licy-presets.ts 100% 100% 0%
src/lib/onboard...conciliation.ts 100% 100% 0%
src/lib/onboard...cy-selection.ts 98% 98% 0%
src/lib/sandbox...rce-identity.ts 82% 82% 0%

Updated August 31, 2026 16:27 UTC

@hunglp6d
hunglp6d marked this pull request as ready for review August 27, 2026 12:45
@hunglp6d hunglp6d self-assigned this Aug 27, 2026
@hunglp6d hunglp6d added area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow v0.0.115 labels Aug 27, 2026
@copy-pr-bot

copy-pr-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

hunglp6d and others added 2 commits August 28, 2026 16:07
Three rebuild tests each rebuilt the full SandboxMessagingPlan shape and differed
only by agent and channel activity, so a schema change needed three synchronized
edits. Give the file one plan factory and one channel factory, both typed from
RebuildBackupPhaseInput so they track the production shape.

Assertions are unchanged. The suite still fails when the shared pruning helper is
a no-op, and the OpenClaw rows still fail when rebuild reads the registry agent
instead of the messaging plan.

Refs #10153

Signed-off-by: Hung Le <hple@nvidia.com>
@hunglp6d

Copy link
Copy Markdown
Collaborator Author

Advisor findings for 4c68993 triaged, then cross-reviewed independently.

Code / Reduction — fixed. The three rebuild tests each rebuilt the full messaging-plan shape. rebuild-backup-phase.test.ts now has one plan factory and one channel factory derived from RebuildBackupPhaseInput. Assertions unchanged, 38 lines shorter, and the suite still fails when the shared pruning helper is mutated to a no-op.

Behavior — not reproduced, no change. normalizeRebuildWebSearchPolicyPresets runs immediately after the pruning step and already drops any preset whose name matches a custom policy (rebuild-backup-phase.ts:81-90) — the same correction the finding proposes. activeMessagingPolicyPresets is a function-local intermediate consumed only by that normalizer. The test the finding cites passes, and still passes with the pruner mutated to a no-op.

One related edge case is disclosed in the description rather than fixed here: custom-preset names are compared lowercased by the pruner and verbatim by the rebuild normalization and restore boundaries. It predates this PR and needs malformed persisted state to reach.

The other eight specialists reported no finding.

The previous commit removed repeated plan literals by adding a factory local to
rebuild-backup-phase.test.ts, which left three owners of the SandboxMessagingPlan
shape: the shared fixture in test/helpers, the backup-phase local factory, and the
rebuild-flow local factory.

Extend makeMessagingPlan additively instead, then delete both local owners:

- channelOverrides expresses a configured channel the rebuild workflow planner
  recomputes as inactive while `disabled` stays false, which the channel and
  disabled lists cannot express on their own. Override inputs are cloned so
  repeated calls and caller-owned inputs stay isolated, and the #8357 isolation
  test now covers that option.
- messagingCredentialBindingsForChannels resolves bindings from the channel
  manifests, so a fixture with active credential-bearing channels satisfies
  enforceMessagingChannelConflicts without restating manifest data. Its doc names
  them synthetic conflict-preflight bindings, because providerName stays a
  template and each hash is a deterministic sentinel.

Existing callers pass neither option and are unchanged. All fourteen consumers of
the shared fixture pass. The rebuild suites still fail when the shared pruning
helper is a no-op, and the OpenClaw rows still fail when rebuild reads the registry
agent instead of the messaging plan.

Refs #10153

Signed-off-by: Hung Le <hple@nvidia.com>
@hunglp6d

Copy link
Copy Markdown
Collaborator Author

Advisor findings for 82abbec triaged. Nine specialists reported no finding.

Code / Reduction — accepted, fixed. The finding is right that test/helpers/messaging-plan-fixtures.ts already owns this schema and that my previous commit left three owners. makeMessagingPlan now takes channelOverrides, for a configured channel the rebuild workflow planner recomputes as inactive while disabled stays false, and the module exports messagingCredentialBindingsForChannels, which resolves bindings from the channel manifests. Both local fixture builders are deleted and both rebuild suites construct plans through the shared owner. Existing callers pass neither option, so their plans are unchanged; all fourteen consumers pass (324 tests).

Cross-review of that consolidation found two things worth fixing, both addressed here: the new override assigned the caller's inputs array by reference, which broke the module's stated #8357 isolation invariant, so it is cloned and the isolation test now covers the option; and the option's comment attributed the inactive-but-not-disabled shape to a fresh manifest compile, which actually marks such a channel disabled (manifest-compiler.ts), so it now names the rebuild workflow planner instead.

Unrelated red check: PR exact OpenClaw managed-image MCP discovery (pass 2) failed on 82abbec while pass 1 of the same job, on the same commit, started one second apart, passed. Both passes were green at the previous head 4c68993. The failure is in test/e2e/live/mcp-bridge.test.ts phase 3/5 and is unrelated to this test-fixture change.

The shared fixture gained a `channelOverrides` option with one consumer, so the
rebuild test's inactive-channel state stopped being local to the behavior it
verifies while the shared contract grew an option nobody else needs.

Build that plan in the test instead: take the ordinary plan from makeMessagingPlan
and map the Discord channel onto an inactive one carrying its unavailable required
secret. The shared fixture returns to its previous shape, and its isolation test
returns to covering the options it still has.

Also correct two comments. The application-boundary comment called
policy-resume-selection.test.ts end-to-end coverage when it is a source test, and
the shared-fixture comments carried background that review context already records.

Refs #10153

Signed-off-by: Hung Le <hple@nvidia.com>
@hunglp6d

Copy link
Copy Markdown
Collaborator Author

Advisor findings for 1b8094a triaged. Seven specialists reported no finding.

Code / Reduction — accepted. The channelOverrides option I added in the previous round had one consumer, so the rebuild test's inactive-channel state was no longer local to the behavior it verifies. That test now builds its plan from makeMessagingPlan and maps the Discord channel onto an inactive one carrying its unavailable required secret. The option, its exported type, its cloning path, and its dedicated isolation case are removed, so the shared fixture returns to its previous contract. Net 36 lines smaller.

For the record, this reverses the direction of the previous round's Code / Reduction finding, which asked for consolidation into the shared fixture. The current shape satisfies both: plan construction still goes through the shared owner, and the one-off state stays in the test that needs it.

Documentation — accepted. The comment said end-to-end coverage lives in policy-resume-selection.test.ts, which is a source test rather than an E2E target. It now says that file covers the preparation step.

Dependency use — declined. The finding states that messagingCredentialBindingsForChannels "has no checked-in caller". It does: src/lib/actions/sandbox/rebuild-flow-lifecycle.test.ts:631. The rest of the finding asks to rewrite messagingPlanLiteral onto the manifest-derived helper and delete CREDENTIAL_BINDINGS. That map has its own consumers and materialized values for literal fixtures, the two serve different purposes, and replacing it would change fixture output for tests unrelated to this PR. Recorded as a possible follow-up rather than folded in here.

Verification: all fourteen consumers of the shared fixture pass (323 tests), growth guardrails 32 passed, npm run typecheck:cli and npm run checks:repository clean, git diff --check clean. The rebuild rows still fail when the shared pruning helper is a no-op, and the cannot-start row still fails when rebuild filters on disabled alone.

@hunglp6d

Copy link
Copy Markdown
Collaborator Author

Advisor findings for 579adea triaged. Eight specialists reported no finding.

Behavior — not reproduced, no change. This repeats the finding declined against 4c68993. Re-verified on this commit: the test it cites passes, and it still passes with pruneInactiveMessagingPolicyPresets mutated to return its input, so the discord removal does not come from the pruner. It comes from the step the finding says does not happen — normalizeRebuildWebSearchPolicyPresets runs straight after pruning and drops any preset whose name matches a custom policy (rebuild-backup-phase.ts:87-90), which is the same correction the finding proposes. activeMessagingPolicyPresets is a function-local intermediate consumed only by that normalizer.

The finding notes it did not run tests. Two independent cross-reviews reached the same conclusion, the second tracing rebuild-pipeline.ts and rebuild-restore-phase.ts for any consumer of the pre-normalized list and finding none. I am not changing the cited assertion, because it asserts the behavior the code has.

@apurvvkumaria apurvvkumaria self-assigned this Aug 30, 2026
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Use the messaging-owned selector during rebuild. Policy pruning now matches all channel effects.

Clarify the known-state and custom-policy conditions in the pruning comments.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
…ssaging-presets

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
…ssaging-presets

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
…ssaging-presets

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Candidate E2E is blocked by the current trusted workflow boundary.

Issue #10153 requires openclaw-slack-pairing to reach and pass the Socket Mode probe. The manual PR workflow does not permit that target selector. Its PR mode also withholds the NVIDIA inference credential that the target requires.

I did not dispatch a broader run because it cannot supply the required evidence. A maintainer decision is required:

  • Accept deterministic coverage and require the target after merge on main.
  • Add an approved candidate-run route for this target.

All repository gates and the required E2E decision remain pending.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Merge-train update

The latest PR commit now includes current main. Focused validation passed 92 tests. CLI type-checking, repository checks, validate:pr, normal hooks, DCO, and GitHub commit verification also pass.

Correction to the earlier E2E note: the current trusted workflow can provide applicable credentialed profiles for this NVIDIA-owned PR branch. However, it can reach openclaw-slack-pairing only through the complete default PR selection. That broad run exposes applicable long-lived credentials to candidate-controlled jobs and can create external resources. A focused Slack selector is not authorized.

I did not dispatch the broad run. A maintainer must choose one action:

  • Authorize the complete default PR E2E selection.
  • Accept the deterministic evidence and require the Slack target after merge on main.

Fresh CI and automated review are running. The E2E decision and independent approval remain blocking.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Merge-train update after the current-main refresh

The provider-authority correction is now on the branch at revision 5d8758b:

  • OpenShell create options before the startup-command separator are the only provider authority. Startup command text cannot authorize a provider.
  • Regression tests cover option parsing, provider insertion, and rejection of a live credential binding named only in startup text.
  • Local validation passed: 93 focused tests, CLI type-checking, repository checks, validate:pr, normal hooks, and the secret scan.
  • Complete credential-boundary security review: PASS. The change adds no credential values and preserves custom-policy ownership and provider identity gates.
  • GitHub marks every PR commit as Verified. DCO passes.

One architecture decision remains. Accepted issue #10514 makes OpenShell the only durable policy source and forbids NemoClaw from storing a desired policy tier. Ordinary resume therefore has no authoritative tier. Two behaviors are possible:

  • Preserve live brave and tavily rules without tier provenance. This avoids narrowing Balanced or Open policy, but it can retain stale web-search egress after a disable or switch.
  • Prune those rules without tier provenance. This removes stale egress, but it can incorrectly narrow Balanced or Open policy.

I did not add tier state or select either behavior without maintainer agreement.

Correction to earlier E2E notes: the current trusted workflow supports the focused selector openclaw-slack-pairing in mock inference mode. It uses fake Slack tokens and the trusted NVIDIA inference credential. A complete default run is not needed. I did not dispatch it.

Fresh CI and Advisor are running. The tier decision, focused E2E decision, and independent approval block merge.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@github-actions

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown
Contributor

PR Review Advisor finished for commit 64591d4. Include the Advisor findings in the complete PR feedback collection. Verify and group valid findings before repair.

All previous runs

@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Final merge-train state for branch revision 64591d44

All current automated checks completed without failure against base and workflow revision 67d6abeb.

  • Core CI passed all 12 CLI shards, merged coverage, static checks, build and type checks, plugin tests, installer integration, audits, and the aggregate check.
  • Managed-image checks passed for both Pi architectures, direct startup for all three agents, all-agent runtime activation, and both OpenClaw MCP discovery passes.
  • Documentation preview, CLI parity, CodeQL, security scanning, CodeRabbit, and all Advisor jobs passed.
  • Local validation passed 93 focused tests, CLI type checking, repository checks, npm run validate:pr, and documentation builds for the OpenClaw and Hermes variants.
  • The credential-boundary security review is PASS. Provider authority now comes only from create options before the startup-command separator. Regression tests cover startup text, required-provider insertion, and credential-binding rejection.
  • The Advisor documentation note does not require a change. PersistedSandboxMessagingPlan omits networkPolicy, compaction removes it, and the persistence regression proves that legacy policy references are discarded and regenerated from current manifests.
  • DCO and GitHub verification pass for every commit. The 661-line diff remains flagged as a large change.

One architecture decision remains under #10514. OpenShell is the only durable policy source, and the desired policy tier is not persisted. Resume therefore has no tier provenance. Preserving live brave and tavily policy entries can retain stale web-search access. Pruning them can incorrectly narrow a Balanced or Open sandbox. This PR does not choose between those outcomes without maintainer direction.

The trusted manual PR workflow can select only openclaw-slack-pairing with inference_mode=mock, no job selectors, no staging Launchable, and no Jetson or DGX queue. It would use Slack fixture values and the workflow inference credential. It was not dispatched because the architecture decision remains open.

Independent approval and the architecture decision still block merge. No merge or bypass was attempted.

@github-actions github-actions Bot added v0.0.119 and removed v0.0.118 labels Sep 1, 2026
@github-actions github-actions Bot added v0.0.120 Release target and removed v0.0.119 labels Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow v0.0.120 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants