Skip to content

refactor(onboard): extract final flow composition - #8264

Merged
cv merged 94 commits into
mainfrom
codex/7695-final-composition
Aug 5, 2026
Merged

refactor(onboard): extract final flow composition#8264
cv merged 94 commits into
mainfrom
codex/7695-final-composition

Conversation

@cv

@cv cv commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Extract final onboarding flow composition from src/lib/onboard.ts into the machine layer. Runtime behavior stays unchanged, while finalization dependencies gain one owner and the architecture guardrails ratchet downward.

Related Issue

Refs #7695

Changes

  • Add a final flow composition module that supplies recovery and readiness dependencies to the existing phase factory.
  • Move finalization runtime dependencies next to their machine-layer consumer and update the live-slice probe.
  • Ratchet the onboarding fan-out budget from 223 to 222 and the root-file budget from 309 to 308.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: This change only moves internal composition and ratchets architecture budgets. It does not change commands, configuration, output, workflows, defaults, errors, or supported behavior.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: Nine-category security review passed for PR commit 7b97b0158dd1dfb16f33e4cee2663ab3df353ad1 against base commit 89e17bfe9a22ffc6b16c8407ce83a2c0a493ba74; no findings.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Security Review

  • Verdict: PASS; no findings.
  • Reviewed commit: 7b97b0158dd1dfb16f33e4cee2663ab3df353ad1.
  • Base commit: 89e17bfe9a22ffc6b16c8407ce83a2c0a493ba74.
  • Secrets and credentials: PASS; no secret storage, credential values, environment forwarding, or logging changed.
  • Input validation and data sanitization: PASS; no input boundary changed.
  • Authentication and authorization: PASS; finalization still invokes the same scoped auto-pair approval and warm-up handlers.
  • Dependencies and third-party libraries: PASS; no dependency or artifact changed.
  • Error handling and logging: PASS; readiness, recovery, and deployment-exit behavior are unchanged.
  • Cryptography and data protection: PASS; no cryptographic or persisted-data behavior changed.
  • Configuration and security headers: PASS; no network policy, service exposure, permission, image, or HTTP configuration changed.
  • Security testing: PASS; the composition test pins the recovery and readiness dependency set, relocated handler tests remain intact, the live-slice probe follows the moved module, and exact-head GitHub checks pass for build/typecheck, static checks, CLI tests and all eight shards, installer integration, plugin tests, WSL/macOS tests, CodeQL, architecture guardrails, DCO, commit lint, and both automated advisors. The authoritative live E2E gate is still running and is tracked separately as a merge gate.
  • System security: PASS; the composition layer injects the same finalization handlers previously supplied by src/lib/onboard.ts, with the option type preventing callers from replacing those owned handlers.

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: no-docs-needed
  • Evidence: Independently reviewed the seven-file effective diff for PR commit 7b97b0158dd1dfb16f33e4cee2663ab3df353ad1 against base commit 89e17bfe9a22ffc6b16c8407ce83a2c0a493ba74. The change only relocates finalization dependencies into the machine layer, adds a composition wrapper that injects the same recovery and readiness dependencies, updates imports and tests, and ratchets architecture budgets. Commands, configuration, defaults, output, errors, workflows, and supported behavior remain unchanged. The composition regression test verifies the dependency invariant and uses a behavior-oriented title; the shortened import-cycle comment follows the writing rules. No docs/ or fern/ files changed. The prior and refreshed patches have the same stable patch ID, every substantive commit is unchanged in git range-diff, and git diff --check passed. Exact-head GitHub checks pass for build/typecheck, static checks, CLI tests and all eight shards, installer integration, plugin tests, WSL/macOS tests, CodeQL, architecture guardrails, DCO, commit lint, and automated reviews. The live E2E gate remains pending; that does not change the documentation-impact verdict.
  • Agent: Codex Desktop

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit: Not applicable
  • Station profile/scenario: Not applicable
  • Result: Not applicable; scripts/prepare-dgx-station-host.sh is unchanged.
  • Supporting evidence: Not applicable

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run validate:pr passed after refreshing origin/main when hooks were skipped or unavailable — pre-commit and commit-msg passed. The linked worktree lacks generated dist/ artifacts required by pre-push; GitHub Actions is the validation authority.
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — Focused CLI Vitest passed 15 tests across checkpoint recording, core-flow composition, final-flow composition, and finalization dependencies. test/onboard-fsm-live-slices.test.ts passed 13 integration tests. npm run typecheck:cli and normal pre-commit, commit-msg, and pre-push hooks passed for PR commit 558415d50555844e539b2d101791b1827199ff0a; the clean merge-main refresh to 7b97b0158dd1dfb16f33e4cee2663ab3df353ad1 preserves every substantive commit, and exact-head CI repeats the applicable source checks successfully.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result: Core GitHub checks passed for commit 7b97b0158dd1dfb16f33e4cee2663ab3df353ad1; the required E2E gate is still running.
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Carlos Villela cvillela@nvidia.com

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability of the onboarding finalization flow by ensuring required completion actions and recovery steps are resolved correctly.
    • Updated onboarding runtime loading to support relocated finalization components without disrupting live flow checks.
  • Tests
    • Added coverage confirming that finalization dependencies are combined correctly and completion phases are preserved.
  • Chores
    • Adjusted architecture thresholds to reflect the current onboarding structure.

cv and others added 30 commits July 28, 2026 01:17
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
@cv cv self-assigned this Aug 4, 2026
@copy-pr-bot

copy-pr-bot Bot commented Aug 4, 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 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds a final onboarding flow composition module, updates onboarding imports, relocates finalization dependency paths, updates related tests and probes, and lowers architecture budget thresholds.

Changes

Final onboarding flow composition

Layer / File(s) Summary
Onboarding flow composition and module wiring
src/lib/onboard/machine/final-flow-composition.ts, src/lib/onboard/machine/final-flow-composition.test.ts, src/lib/onboard.ts
The new composition factory merges caller dependencies with finalizationHandlerDeps. onboard.ts uses the composed final-flow APIs. Tests verify dependency composition and result propagation.
Finalization dependency paths and validation
src/lib/onboard/machine/finalization-deps.ts, src/lib/onboard/machine/finalization-deps.test.ts, test/onboard-fsm-live-slices.test.ts, ci/source-architecture-budget.json
Lazy dependency loaders, type imports, and the live probe use relocated module paths. Architecture budgets decrease for onboarding fan-out and root files.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Onboard as onboard.ts
  participant Composition as final-flow-composition
  participant Phases as final-flow-phases
  Onboard->>Composition: createFinalOnboardFlowPhases(options)
  Composition->>Composition: merge finalizationHandlerDeps
  Composition->>Phases: create final flow phases
  Phases-->>Composition: return phases
  Composition-->>Onboard: return phases
Loading

Possibly related PRs

  • NVIDIA/NemoClaw#8263: Refactors onboarding flow composition into dedicated machine-layer modules and adjusts architecture budgets.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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 describes the extraction of final flow composition from the onboarding module.
✨ 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 codex/7695-final-composition

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

@github-code-quality

github-code-quality Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit 7b97b01 in the codex/7695-final-com... branch remains at 96%, unchanged from commit 89e17bf in the main branch.

TypeScript / code-coverage/cli

The overall coverage in commit 7b97b01 in the codex/7695-final-com... branch remains at 81%, unchanged from commit 89e17bf in the main branch.

Show a code coverage summary of the most impacted files.
File main 89e17bf codex/7695-final-com... 7b97b01 +/-
src/lib/onboard...ization-deps.ts 83% 0% -83%
src/lib/onboard.ts 31% 31% 0%
src/lib/messagi...nnels/policy.ts 100% 100% 0%
src/lib/sandbox...rce-identity.ts 87% 87% 0%
src/lib/platform.ts 84% 89% +5%
src/lib/onboard...ization-deps.ts 0% 83% +83%
src/lib/onboard...-composition.ts 0% 100% +100%

Updated August 05, 2026 08:14 UTC

@wscurran wscurran added area: architecture Architecture, design debt, major refactors, or maintainability area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow refactor PR restructures code without intended behavior change labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Model comparison: normalized findings match; normalized terminology decisions differ; normalized E2E selections differ; severity counts match.
1 terminology difference from the second opinion

Advisory only. These are normalized differences from the primary terminology receipt.

  • final flow composition at src/lib/onboard.ts:487: selected only by the second-opinion lane as justified.

Second-opinion terminology and E2E selections are advisory. They do not change the primary assessment or E2E / PR Gate.

E2E guidance

Advisory only. E2E / PR Gate selects and runs jobs independently.

Recommended E2E: cloud-onboard, onboard-repair, onboard-resume

1 optional E2E recommendation
  • issue-4462-scope-upgrade-approval

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

Base automatically changed from codex/7695-core-composition to main August 5, 2026 06:59
@cv
cv marked this pull request as ready for review August 5, 2026 07:00
cv and others added 2 commits August 5, 2026 00:02
@copy-pr-bot

copy-pr-bot Bot commented Aug 5, 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.

Signed-off-by: Carlos Villela <cvillela@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/lib/onboard/machine/handlers/sandbox-messaging.ts (2)

15-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer a single import path for resolveMessagingPlanAuthority.

This module re-exports resolveMessagingPlanAuthority from ../../../messaging/plan-authority. src/lib/onboard/channel-state.ts imports the same symbol directly from the messaging module, so the codebase now has two import paths for one owner. Import it directly in src/lib/onboard/machine/handlers/sandbox.ts and drop the re-export, unless the re-export exists for a bounded compatibility window.

♻️ Proposed change
 import {
   type RegistryMessagingAuthority,
   resolveMessagingPlanAuthority,
 } from "../../../messaging/plan-authority";
 import { getActiveChannelsFromPlan, getChannelsFromPlan } from "../../messaging-plan-session";
-
-export { resolveMessagingPlanAuthority };

Then in src/lib/onboard/machine/handlers/sandbox.ts, import resolveMessagingPlanAuthority from ../../../messaging/plan-authority instead of ./sandbox-messaging.

As per path instructions: "Flag cross-layer cycles, duplicate sources of truth, and forwarding wrappers that add a new layer without retiring the old owner and its callers."

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-messaging.ts` around lines 15 - 22,
Remove the resolveMessagingPlanAuthority re-export from sandbox-messaging.ts and
update sandbox.ts to import it directly from ../../../messaging/plan-authority.
Preserve the existing RegistryMessagingAuthority import and other
sandbox-messaging usages, leaving only the messaging module as the symbol’s
import path.

Source: Path instructions


450-457: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unreachable registryPlan path from selectionFromRecordedChannels. This is the only caller, and it always passes null for registryPlan; remove the parameter and its else if (registryPlan) branch.

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-messaging.ts` around lines 450 -
457, Update selectionFromRecordedChannels and its sole caller in the
recordedChannels flow to remove the unused registryPlan parameter, including the
null argument, and delete the corresponding else if (registryPlan) branch while
preserving the remaining selection behavior.
src/lib/onboard/machine/handlers/sandbox-create-intent-boundary.test.ts (2)

330-331: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The ordering assertion passes when setupMessagingChannels never runs.

invocationCallOrder[0] ?? Number.NEGATIVE_INFINITY makes the comparison succeed if messaging setup was never called. Assert the setup call first, then compare the orders.

💚 Proposed test fix
+    expect(setupMessagingChannels).toHaveBeenCalled();
     expect(stageSandboxCredentialProviders.mock.invocationCallOrder[0]).toBeGreaterThan(
-      setupMessagingChannels.mock.invocationCallOrder[0] ?? Number.NEGATIVE_INFINITY,
+      setupMessagingChannels.mock.invocationCallOrder[0]!,
     );

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-create-intent-boundary.test.ts`
around lines 330 - 331, Update the ordering assertion in the sandbox creation
test to first require that setupMessagingChannels was called, then compare its
invocation order with stageSandboxCredentialProviders. Remove the
Number.NEGATIVE_INFINITY fallback so the test cannot pass when messaging setup
is skipped.

Source: Path instructions


202-211: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the plan-authority expectation explicit instead of calling the resolver inside the mock.

getStoredMessagingChannelConfig calls resolveMessagingPlanAuthority and discards the result. The call only affects the test if it throws, so the intent is hidden. Lines 229-232 already assert the sandbox name and the cleared plan. Either assert the resolver explicitly or remove the call.

💚 Proposed test fix
     const getStoredMessagingChannelConfig = vi.fn((sandboxName: string | null) => {
-      expect(sandboxName).toBe("new-name");
-      resolveMessagingPlanAuthority({
-        sandboxName: sandboxName ?? "",
-        registry: { authoritative: false, plan: null },
-        stagedPlan,
-        sessionPlan: durableSession.messagingPlan,
-      });
+      expect(() =>
+        resolveMessagingPlanAuthority({
+          sandboxName: sandboxName ?? "",
+          registry: { authoritative: false, plan: null },
+          stagedPlan,
+          sessionPlan: durableSession.messagingPlan,
+        }),
+      ).not.toThrow();
       return null;
     });

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-create-intent-boundary.test.ts`
around lines 202 - 211, Remove the discarded resolveMessagingPlanAuthority call
from the getStoredMessagingChannelConfig mock, since the mock should only
provide its stored-config behavior. Keep the existing assertions around the
resolver’s expected plan authority, sandbox name, and cleared plan explicit in
the test rather than relying on an exception from the mock.

Source: Path instructions

src/lib/onboard/machine/handlers/sandbox-provider-effect-replay.test.ts (1)

352-366: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Initialize currentBindingLive to false so staging proves the binding became live.

currentBindingLive starts as true, and stageSandboxCredentialProviders only sets it to true again. The gateway match therefore already succeeds before staging runs, so the test cannot show that staging made the current binding live. The sibling replay test in sandbox-checkpoint-crash-recovery.test.ts starts its flag at false.

💚 Proposed test fix
-    let currentBindingLive = true;
+    let currentBindingLive = false;
     const stageSandboxCredentialProviders = vi.fn(async () => {
       currentBindingLive = true;
       return [currentBinding];
     });

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-provider-effect-replay.test.ts`
around lines 352 - 366, Initialize currentBindingLive to false in the test
setup, while keeping stageSandboxCredentialProviders responsible for setting it
to true. This ensures providerMatchesGatewayCredential only succeeds after
staging and the replay test verifies the intended behavior.

Source: Path instructions

src/lib/onboard/checkpoint-revalidate.ts (1)

30-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the canonical-binding predicate into one shared helper.

The same canonical-binding rule now exists in three modules:

  • Lines 34-40 here.
  • assertValidProviderBindings in src/lib/onboard/checkpoint-record.ts (Lines 29-44).
  • isCanonicalBinding in src/lib/onboard/credential-provider-registration.ts (Lines 87-91).

All three check non-empty and trimmed name, type, and credentialEnv. The three copies must stay in sync. If one copy gains a rule and the others do not, a binding accepted at registration can be rejected at replay, or the reverse.

Move the predicate next to CheckpointProviderBinding in src/lib/state/onboard-checkpoint-types.ts and call it from all three sites. Keep each module's own failure mode: throw, stale report, or plan error.

🤖 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 `@src/lib/onboard/checkpoint-revalidate.ts` around lines 30 - 46, Extract the
shared canonical-binding predicate next to CheckpointProviderBinding in
onboard-checkpoint-types.ts, covering non-empty and trimmed name, type, and
credentialEnv values. Replace the duplicated checks in the missingProviders
filter, assertValidProviderBindings, and isCanonicalBinding with this helper
while preserving each caller’s existing failure behavior.
🤖 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 `@src/lib/onboard/machine/handlers/sandbox-messaging.ts`:
- Around line 378-415: Add a resume-path test for registry authority using the
existing staged-plan divergence test as a template, with checkpoint channels
differing from the authoritative registry plan. Verify setupMessagingChannels is
not called and the registry plan is returned unchanged.

---

Nitpick comments:
In `@src/lib/onboard/checkpoint-revalidate.ts`:
- Around line 30-46: Extract the shared canonical-binding predicate next to
CheckpointProviderBinding in onboard-checkpoint-types.ts, covering non-empty and
trimmed name, type, and credentialEnv values. Replace the duplicated checks in
the missingProviders filter, assertValidProviderBindings, and isCanonicalBinding
with this helper while preserving each caller’s existing failure behavior.

In `@src/lib/onboard/machine/handlers/sandbox-create-intent-boundary.test.ts`:
- Around line 330-331: Update the ordering assertion in the sandbox creation
test to first require that setupMessagingChannels was called, then compare its
invocation order with stageSandboxCredentialProviders. Remove the
Number.NEGATIVE_INFINITY fallback so the test cannot pass when messaging setup
is skipped.
- Around line 202-211: Remove the discarded resolveMessagingPlanAuthority call
from the getStoredMessagingChannelConfig mock, since the mock should only
provide its stored-config behavior. Keep the existing assertions around the
resolver’s expected plan authority, sandbox name, and cleared plan explicit in
the test rather than relying on an exception from the mock.

In `@src/lib/onboard/machine/handlers/sandbox-messaging.ts`:
- Around line 15-22: Remove the resolveMessagingPlanAuthority re-export from
sandbox-messaging.ts and update sandbox.ts to import it directly from
../../../messaging/plan-authority. Preserve the existing
RegistryMessagingAuthority import and other sandbox-messaging usages, leaving
only the messaging module as the symbol’s import path.
- Around line 450-457: Update selectionFromRecordedChannels and its sole caller
in the recordedChannels flow to remove the unused registryPlan parameter,
including the null argument, and delete the corresponding else if (registryPlan)
branch while preserving the remaining selection behavior.

In `@src/lib/onboard/machine/handlers/sandbox-provider-effect-replay.test.ts`:
- Around line 352-366: Initialize currentBindingLive to false in the test setup,
while keeping stageSandboxCredentialProviders responsible for setting it to
true. This ensures providerMatchesGatewayCredential only succeeds after staging
and the replay test verifies the intended behavior.
🪄 Autofix

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: Enterprise

Run ID: 0f95d641-9df3-4861-823f-fb56d53212f3

📥 Commits

Reviewing files that changed from the base of the PR and between 6ee7a4a and ee1290b.

📒 Files selected for processing (43)
  • ci/source-architecture-budget.json
  • docs/security/credential-storage.mdx
  • src/lib/actions/sandbox/rebuild-target-staging.test.ts
  • src/lib/messaging/plan-authority.test.ts
  • src/lib/messaging/plan-authority.ts
  • src/lib/onboard.ts
  • src/lib/onboard/channel-state.test.ts
  • src/lib/onboard/channel-state.ts
  • src/lib/onboard/checkpoint-record.test.ts
  • src/lib/onboard/checkpoint-record.ts
  • src/lib/onboard/checkpoint-replay.test.ts
  • src/lib/onboard/checkpoint-replay.ts
  • src/lib/onboard/checkpoint-revalidate.ts
  • src/lib/onboard/credential-provider-registration.test.ts
  • src/lib/onboard/credential-provider-registration.ts
  • src/lib/onboard/lifecycle-contracts.md
  • src/lib/onboard/machine/core-flow-composition.test.ts
  • src/lib/onboard/machine/core-flow-composition.ts
  • src/lib/onboard/machine/core-flow-phases.test.ts
  • src/lib/onboard/machine/final-flow-composition.test.ts
  • src/lib/onboard/machine/final-flow-composition.ts
  • src/lib/onboard/machine/finalization-deps.test.ts
  • src/lib/onboard/machine/finalization-deps.ts
  • src/lib/onboard/machine/handlers/sandbox-checkpoint-crash-recovery.test.ts
  • src/lib/onboard/machine/handlers/sandbox-create-intent-boundary.test.ts
  • src/lib/onboard/machine/handlers/sandbox-messaging.test.ts
  • src/lib/onboard/machine/handlers/sandbox-messaging.ts
  • src/lib/onboard/machine/handlers/sandbox-provider-effect-replay.test.ts
  • src/lib/onboard/machine/handlers/sandbox-test-fixtures.ts
  • src/lib/onboard/machine/handlers/sandbox.test.ts
  • src/lib/onboard/machine/handlers/sandbox.ts
  • src/lib/onboard/machine/resume-provider-shim.test.ts
  • src/lib/onboard/machine/resume-provider-shim.ts
  • src/lib/onboard/messaging-channel-setup.test.ts
  • src/lib/onboard/messaging-channel-setup.ts
  • src/lib/onboard/messaging-config.test.ts
  • src/lib/onboard/messaging-config.ts
  • src/lib/onboard/messaging-credentials.ts
  • src/lib/onboard/messaging-reuse.test.ts
  • src/lib/onboard/messaging-reuse.ts
  • test/channels-add-preset.test.ts
  • test/credential-migration-reconciliation.test.ts
  • test/onboard-fsm-live-slices.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/lib/onboard/machine/handlers/sandbox-messaging.ts (2)

15-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer a single import path for resolveMessagingPlanAuthority.

This module re-exports resolveMessagingPlanAuthority from ../../../messaging/plan-authority. src/lib/onboard/channel-state.ts imports the same symbol directly from the messaging module, so the codebase now has two import paths for one owner. Import it directly in src/lib/onboard/machine/handlers/sandbox.ts and drop the re-export, unless the re-export exists for a bounded compatibility window.

♻️ Proposed change
 import {
   type RegistryMessagingAuthority,
   resolveMessagingPlanAuthority,
 } from "../../../messaging/plan-authority";
 import { getActiveChannelsFromPlan, getChannelsFromPlan } from "../../messaging-plan-session";
-
-export { resolveMessagingPlanAuthority };

Then in src/lib/onboard/machine/handlers/sandbox.ts, import resolveMessagingPlanAuthority from ../../../messaging/plan-authority instead of ./sandbox-messaging.

As per path instructions: "Flag cross-layer cycles, duplicate sources of truth, and forwarding wrappers that add a new layer without retiring the old owner and its callers."

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-messaging.ts` around lines 15 - 22,
Remove the resolveMessagingPlanAuthority re-export from sandbox-messaging.ts and
update sandbox.ts to import it directly from ../../../messaging/plan-authority.
Preserve the existing RegistryMessagingAuthority import and other
sandbox-messaging usages, leaving only the messaging module as the symbol’s
import path.

Source: Path instructions


450-457: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unreachable registryPlan path from selectionFromRecordedChannels. This is the only caller, and it always passes null for registryPlan; remove the parameter and its else if (registryPlan) branch.

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-messaging.ts` around lines 450 -
457, Update selectionFromRecordedChannels and its sole caller in the
recordedChannels flow to remove the unused registryPlan parameter, including the
null argument, and delete the corresponding else if (registryPlan) branch while
preserving the remaining selection behavior.
src/lib/onboard/machine/handlers/sandbox-create-intent-boundary.test.ts (2)

330-331: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The ordering assertion passes when setupMessagingChannels never runs.

invocationCallOrder[0] ?? Number.NEGATIVE_INFINITY makes the comparison succeed if messaging setup was never called. Assert the setup call first, then compare the orders.

💚 Proposed test fix
+    expect(setupMessagingChannels).toHaveBeenCalled();
     expect(stageSandboxCredentialProviders.mock.invocationCallOrder[0]).toBeGreaterThan(
-      setupMessagingChannels.mock.invocationCallOrder[0] ?? Number.NEGATIVE_INFINITY,
+      setupMessagingChannels.mock.invocationCallOrder[0]!,
     );

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-create-intent-boundary.test.ts`
around lines 330 - 331, Update the ordering assertion in the sandbox creation
test to first require that setupMessagingChannels was called, then compare its
invocation order with stageSandboxCredentialProviders. Remove the
Number.NEGATIVE_INFINITY fallback so the test cannot pass when messaging setup
is skipped.

Source: Path instructions


202-211: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the plan-authority expectation explicit instead of calling the resolver inside the mock.

getStoredMessagingChannelConfig calls resolveMessagingPlanAuthority and discards the result. The call only affects the test if it throws, so the intent is hidden. Lines 229-232 already assert the sandbox name and the cleared plan. Either assert the resolver explicitly or remove the call.

💚 Proposed test fix
     const getStoredMessagingChannelConfig = vi.fn((sandboxName: string | null) => {
-      expect(sandboxName).toBe("new-name");
-      resolveMessagingPlanAuthority({
-        sandboxName: sandboxName ?? "",
-        registry: { authoritative: false, plan: null },
-        stagedPlan,
-        sessionPlan: durableSession.messagingPlan,
-      });
+      expect(() =>
+        resolveMessagingPlanAuthority({
+          sandboxName: sandboxName ?? "",
+          registry: { authoritative: false, plan: null },
+          stagedPlan,
+          sessionPlan: durableSession.messagingPlan,
+        }),
+      ).not.toThrow();
       return null;
     });

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-create-intent-boundary.test.ts`
around lines 202 - 211, Remove the discarded resolveMessagingPlanAuthority call
from the getStoredMessagingChannelConfig mock, since the mock should only
provide its stored-config behavior. Keep the existing assertions around the
resolver’s expected plan authority, sandbox name, and cleared plan explicit in
the test rather than relying on an exception from the mock.

Source: Path instructions

src/lib/onboard/machine/handlers/sandbox-provider-effect-replay.test.ts (1)

352-366: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Initialize currentBindingLive to false so staging proves the binding became live.

currentBindingLive starts as true, and stageSandboxCredentialProviders only sets it to true again. The gateway match therefore already succeeds before staging runs, so the test cannot show that staging made the current binding live. The sibling replay test in sandbox-checkpoint-crash-recovery.test.ts starts its flag at false.

💚 Proposed test fix
-    let currentBindingLive = true;
+    let currentBindingLive = false;
     const stageSandboxCredentialProviders = vi.fn(async () => {
       currentBindingLive = true;
       return [currentBinding];
     });

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-provider-effect-replay.test.ts`
around lines 352 - 366, Initialize currentBindingLive to false in the test
setup, while keeping stageSandboxCredentialProviders responsible for setting it
to true. This ensures providerMatchesGatewayCredential only succeeds after
staging and the replay test verifies the intended behavior.

Source: Path instructions

src/lib/onboard/checkpoint-revalidate.ts (1)

30-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the canonical-binding predicate into one shared helper.

The same canonical-binding rule now exists in three modules:

  • Lines 34-40 here.
  • assertValidProviderBindings in src/lib/onboard/checkpoint-record.ts (Lines 29-44).
  • isCanonicalBinding in src/lib/onboard/credential-provider-registration.ts (Lines 87-91).

All three check non-empty and trimmed name, type, and credentialEnv. The three copies must stay in sync. If one copy gains a rule and the others do not, a binding accepted at registration can be rejected at replay, or the reverse.

Move the predicate next to CheckpointProviderBinding in src/lib/state/onboard-checkpoint-types.ts and call it from all three sites. Keep each module's own failure mode: throw, stale report, or plan error.

🤖 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 `@src/lib/onboard/checkpoint-revalidate.ts` around lines 30 - 46, Extract the
shared canonical-binding predicate next to CheckpointProviderBinding in
onboard-checkpoint-types.ts, covering non-empty and trimmed name, type, and
credentialEnv values. Replace the duplicated checks in the missingProviders
filter, assertValidProviderBindings, and isCanonicalBinding with this helper
while preserving each caller’s existing failure behavior.
🤖 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 `@src/lib/onboard/machine/handlers/sandbox-messaging.ts`:
- Around line 378-415: Add a resume-path test for registry authority using the
existing staged-plan divergence test as a template, with checkpoint channels
differing from the authoritative registry plan. Verify setupMessagingChannels is
not called and the registry plan is returned unchanged.

---

Nitpick comments:
In `@src/lib/onboard/checkpoint-revalidate.ts`:
- Around line 30-46: Extract the shared canonical-binding predicate next to
CheckpointProviderBinding in onboard-checkpoint-types.ts, covering non-empty and
trimmed name, type, and credentialEnv values. Replace the duplicated checks in
the missingProviders filter, assertValidProviderBindings, and isCanonicalBinding
with this helper while preserving each caller’s existing failure behavior.

In `@src/lib/onboard/machine/handlers/sandbox-create-intent-boundary.test.ts`:
- Around line 330-331: Update the ordering assertion in the sandbox creation
test to first require that setupMessagingChannels was called, then compare its
invocation order with stageSandboxCredentialProviders. Remove the
Number.NEGATIVE_INFINITY fallback so the test cannot pass when messaging setup
is skipped.
- Around line 202-211: Remove the discarded resolveMessagingPlanAuthority call
from the getStoredMessagingChannelConfig mock, since the mock should only
provide its stored-config behavior. Keep the existing assertions around the
resolver’s expected plan authority, sandbox name, and cleared plan explicit in
the test rather than relying on an exception from the mock.

In `@src/lib/onboard/machine/handlers/sandbox-messaging.ts`:
- Around line 15-22: Remove the resolveMessagingPlanAuthority re-export from
sandbox-messaging.ts and update sandbox.ts to import it directly from
../../../messaging/plan-authority. Preserve the existing
RegistryMessagingAuthority import and other sandbox-messaging usages, leaving
only the messaging module as the symbol’s import path.
- Around line 450-457: Update selectionFromRecordedChannels and its sole caller
in the recordedChannels flow to remove the unused registryPlan parameter,
including the null argument, and delete the corresponding else if (registryPlan)
branch while preserving the remaining selection behavior.

In `@src/lib/onboard/machine/handlers/sandbox-provider-effect-replay.test.ts`:
- Around line 352-366: Initialize currentBindingLive to false in the test setup,
while keeping stageSandboxCredentialProviders responsible for setting it to
true. This ensures providerMatchesGatewayCredential only succeeds after staging
and the replay test verifies the intended behavior.
🪄 Autofix

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: Enterprise

Run ID: 0f95d641-9df3-4861-823f-fb56d53212f3

📥 Commits

Reviewing files that changed from the base of the PR and between 6ee7a4a and ee1290b.

📒 Files selected for processing (43)
  • ci/source-architecture-budget.json
  • docs/security/credential-storage.mdx
  • src/lib/actions/sandbox/rebuild-target-staging.test.ts
  • src/lib/messaging/plan-authority.test.ts
  • src/lib/messaging/plan-authority.ts
  • src/lib/onboard.ts
  • src/lib/onboard/channel-state.test.ts
  • src/lib/onboard/channel-state.ts
  • src/lib/onboard/checkpoint-record.test.ts
  • src/lib/onboard/checkpoint-record.ts
  • src/lib/onboard/checkpoint-replay.test.ts
  • src/lib/onboard/checkpoint-replay.ts
  • src/lib/onboard/checkpoint-revalidate.ts
  • src/lib/onboard/credential-provider-registration.test.ts
  • src/lib/onboard/credential-provider-registration.ts
  • src/lib/onboard/lifecycle-contracts.md
  • src/lib/onboard/machine/core-flow-composition.test.ts
  • src/lib/onboard/machine/core-flow-composition.ts
  • src/lib/onboard/machine/core-flow-phases.test.ts
  • src/lib/onboard/machine/final-flow-composition.test.ts
  • src/lib/onboard/machine/final-flow-composition.ts
  • src/lib/onboard/machine/finalization-deps.test.ts
  • src/lib/onboard/machine/finalization-deps.ts
  • src/lib/onboard/machine/handlers/sandbox-checkpoint-crash-recovery.test.ts
  • src/lib/onboard/machine/handlers/sandbox-create-intent-boundary.test.ts
  • src/lib/onboard/machine/handlers/sandbox-messaging.test.ts
  • src/lib/onboard/machine/handlers/sandbox-messaging.ts
  • src/lib/onboard/machine/handlers/sandbox-provider-effect-replay.test.ts
  • src/lib/onboard/machine/handlers/sandbox-test-fixtures.ts
  • src/lib/onboard/machine/handlers/sandbox.test.ts
  • src/lib/onboard/machine/handlers/sandbox.ts
  • src/lib/onboard/machine/resume-provider-shim.test.ts
  • src/lib/onboard/machine/resume-provider-shim.ts
  • src/lib/onboard/messaging-channel-setup.test.ts
  • src/lib/onboard/messaging-channel-setup.ts
  • src/lib/onboard/messaging-config.test.ts
  • src/lib/onboard/messaging-config.ts
  • src/lib/onboard/messaging-credentials.ts
  • src/lib/onboard/messaging-reuse.test.ts
  • src/lib/onboard/messaging-reuse.ts
  • test/channels-add-preset.test.ts
  • test/credential-migration-reconciliation.test.ts
  • test/onboard-fsm-live-slices.test.ts
🛑 Comments failed to post (1)
src/lib/onboard/machine/handlers/sandbox-messaging.ts (1)

378-415: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Locate existing divergence coverage for messaging checkpoints.
rg -nP --type=ts -C5 'divergedCheckpointChannels|Reconciling messaging selection'

Repository: NVIDIA/NemoClaw

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(sandbox-messaging\.ts|.*messaging.*test.*|.*sandbox.*test.*)$' || true
printf '%s\n' '--- helper definitions and callers ---'
rg -n -C4 'selectionFromRegistryAuthority|selectionFromCompletedMessagingAuthority|reconcileCheckpoint|resolveMessagingPlanAuthority|selectionFromCompletedMessagingCheckpoint|messagingDecisionCompleted' src --glob '*.ts' || true
printf '%s\n' '--- relevant file outline ---'
ast-grep outline src/lib/onboard/machine/handlers/sandbox-messaging.ts --match 'selectionFromRegistryAuthority' --view expanded || true

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- sandbox-messaging test symbols ---'
ast-grep outline src/lib/onboard/machine/handlers/sandbox-messaging.test.ts | sed -n '1,220p'
printf '%s\n' '--- authority/checkpoint test references ---'
rg -n -C6 'registry|staged|checkpoint|resume|diverg|Reconciling messaging selection|selectedChannels|clearPlanEnv' \
  src/lib/onboard/machine/handlers/sandbox-messaging.test.ts
printf '%s\n' '--- source control flow ---'
sed -n '250,470p' src/lib/onboard/machine/handlers/sandbox-messaging.ts

Repository: NVIDIA/NemoClaw

Length of output: 24642


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- authoritative resume coverage across onboarding tests ---'
rg -n -C8 'authoritative:\s*true|withMessagingCheckpoint|completedCheckpointSession|Reconciling messaging selection' \
  src/lib/onboard src/lib/actions/sandbox --glob '*.test.ts' | rg -B8 -A8 \
  'authoritative:\s*true|withMessagingCheckpoint|completedCheckpointSession|Reconciling messaging selection' || true
printf '%s\n' '--- focused test blocks ---'
sed -n '286,405p' src/lib/onboard/machine/handlers/sandbox-messaging.test.ts
sed -n '531,585p' src/lib/onboard/machine/handlers/sandbox-messaging.test.ts
printf '%s\n' '--- deterministic divergence check for the staged-authority fixture ---'
python3 - <<'PY'
from pathlib import Path
p = Path("src/lib/onboard/machine/handlers/sandbox-messaging.test.ts").read_text()
start = p.index('it("uses the staged plan before a matching session plan during resume for a pending target"')
end = p.index('\n  it("rejects a staged plan', start)
block = p[start:end]
print("has resume:", "resume: true" in block)
print("has completed checkpoint:", "withMessagingCheckpoint(" in block)
print("checkpoint channels:", '["telegram"]' in block)
print("staged plan channels:", "stagedPlan = slackPlan" in block)
print("setup skipped:", "setupMessagingChannels).not.toHaveBeenCalled()" in block)
PY

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- registry resume tests in the public sandbox handler ---'
sed -n '540,620p' src/lib/onboard/machine/handlers/sandbox.test.ts
sed -n '1185,1240p' src/lib/onboard/machine/handlers/sandbox.test.ts
sed -n '1280,1310p' src/lib/onboard/machine/handlers/sandbox.test.ts
printf '%s\n' '--- checkpoint references in registry-authority test regions ---'
python3 - <<'PY'
from pathlib import Path
for name in [
    "src/lib/onboard/machine/handlers/sandbox-messaging.test.ts",
    "src/lib/onboard/machine/handlers/sandbox.test.ts",
]:
    text = Path(name).read_text().splitlines()
    print(name)
    for i, line in enumerate(text):
        if "getRegistrySandboxMessagingAuthority" in line and "authoritative: true" in "\n".join(text[i:i+12]):
            lo, hi = max(0, i-8), min(len(text), i+24)
            block = "\n".join(text[lo:hi])
            print(f"  lines {lo+1}-{hi}: checkpoint={('checkpoint' in block)}, divergent-marker={('discord' in block and 'telegram' in block)}")
PY

Repository: NVIDIA/NemoClaw

Length of output: 7557


Add registry-authority resume divergence coverage.

The staged-plan test already covers divergent checkpoint channels and skips setup. Add the equivalent resume case for an authoritative registry plan. Assert that setupMessagingChannels is not called and that the registry plan is returned.

🤖 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 `@src/lib/onboard/machine/handlers/sandbox-messaging.ts` around lines 378 -
415, Add a resume-path test for registry authority using the existing
staged-plan divergence test as a template, with checkpoint channels differing
from the authoritative registry plan. Verify setupMessagingChannels is not
called and the registry plan is returned unchanged.

Source: Path instructions

@cv
cv merged commit fee8a24 into main Aug 5, 2026
65 of 67 checks passed
@cv
cv deleted the codex/7695-final-composition branch August 5, 2026 08:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: architecture Architecture, design debt, major refactors, or maintainability area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow refactor PR restructures code without intended behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants