From 9b00b9c3a922df3068c2a7c778f0948df9d5b51a Mon Sep 17 00:00:00 2001 From: PlzTouchGrass <130718671+PlzTouchGrass@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:20:43 -0400 Subject: [PATCH] fix(cli): register the trust command on the root (dropped in #162) Program.CommandRegistration.cs built the command tree with BuildTrustCommand(jsonOpt) but the AddCommand block at the bottom never added it to the root, so `nexo trust ...` failed with "'trust' was not matched" (TreatUnmatchedTokensAsErrors). The registration line was lost in ae6c6bfb (#162) when the file was split. Consequences of the gap: - README.md ("trust dashboard", "trust pack apply --id ...", "trust pause/resume") and docs/GettingStarted.md ("help should list trust") advertised a command that did not exist. - scripts/security-gate-tier-c.sh runs `trust boundary --format-json` and `trust dashboard --format-json` under `set -euo pipefail`, so `make security-gate-tier-c` and the Security Gate workflow's Tier C were guaranteed to fail. Fix: add root.AddCommand(trustCmd) beside the other AddCommand calls. All documented subcommands (dashboard, boundary, pause, resume, pack apply --id) already exist in Program.TrustCommands.cs; nothing new is invented. Guard: BuildRootCommand is now `internal` (Nexo.CLI already has InternalsVisibleTo Nexo.Tests.CLI) and RootCommandRegistrationTests asserts the root registers `trust` and that `trust` exposes the subcommands the docs and Tier C name. This catches the built-but-never-added failure mode without spawning the host. CI: security-gate.yml's pull_request path filter now includes Program.TrustCommands.cs and Program.CommandRegistration.cs so future edits to the trust wiring re-run the gate. Co-Authored-By: Claude Opus 5 --- .github/workflows/security-gate.yml | 2 + .../Nexo.CLI/Program.CommandRegistration.cs | 8 +++- .../Commands/RootCommandRegistrationTests.cs | 39 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 application/src/Nexo.Tests.CLI/Tests/Commands/RootCommandRegistrationTests.cs diff --git a/.github/workflows/security-gate.yml b/.github/workflows/security-gate.yml index dae330d76..afec0004a 100644 --- a/.github/workflows/security-gate.yml +++ b/.github/workflows/security-gate.yml @@ -13,6 +13,8 @@ on: - "src/Nexo.Tests.Infrastructure/Tests/Safety/**" - "application/src/Nexo.API/Security/**" - "application/src/Nexo.CLI/Commands/TrustCommand.cs" + - "application/src/Nexo.CLI/Program.TrustCommands.cs" + - "application/src/Nexo.CLI/Program.CommandRegistration.cs" - "scripts/security-gate*.sh" - "Makefile" - ".github/workflows/security-gate.yml" diff --git a/application/src/Nexo.CLI/Program.CommandRegistration.cs b/application/src/Nexo.CLI/Program.CommandRegistration.cs index 59b08e005..d2c5c42e3 100644 --- a/application/src/Nexo.CLI/Program.CommandRegistration.cs +++ b/application/src/Nexo.CLI/Program.CommandRegistration.cs @@ -10,7 +10,12 @@ namespace Nexo.CLI; /// Program. static partial class Program { - private static RootCommand BuildRootCommand() + /// + /// Builds the CLI root command tree. Internal so Nexo.Tests.CLI can assert the + /// registered top-level commands without spawning the host (guards against a + /// built-but-never-added command, which is how `trust` went missing in #162). + /// + internal static RootCommand BuildRootCommand() { var root = new RootCommand("Nexo command-line interface") { @@ -609,6 +614,7 @@ private static RootCommand BuildRootCommand() var meshCmd = new MeshCommand(); root.AddCommand(meshCmd); root.AddCommand(backgroundAgentCmd); + root.AddCommand(trustCmd); root.AddCommand(testCmd); root.AddCommand(escalateCmd); root.AddCommand(metricsCmd); diff --git a/application/src/Nexo.Tests.CLI/Tests/Commands/RootCommandRegistrationTests.cs b/application/src/Nexo.Tests.CLI/Tests/Commands/RootCommandRegistrationTests.cs new file mode 100644 index 000000000..c77d805ac --- /dev/null +++ b/application/src/Nexo.Tests.CLI/Tests/Commands/RootCommandRegistrationTests.cs @@ -0,0 +1,39 @@ +using System.CommandLine; +using FluentAssertions; +using Xunit; + +namespace Nexo.Tests.CLI.Tests.Commands; + +/// +/// Guards the root command registration list in Program.CommandRegistration.cs. +/// A command can be built (BuildXxxCommand) and then silently never added to the root; +/// that is how `nexo trust` disappeared in #162 while README/GettingStarted and the +/// Tier C security gate kept invoking it. +/// +[Trait("Category", "CLI")] +public sealed class RootCommandRegistrationTests +{ + [Fact(Timeout = 15000)] + public async Task RootCommand_RegistersTrustCommand() + { + await Task.CompletedTask; + var root = Nexo.CLI.Program.BuildRootCommand(); + var subcommands = root.Subcommands.Select(s => s.Name).ToList(); + + subcommands.Should().Contain("trust"); + } + + [Fact(Timeout = 15000)] + public async Task TrustCommand_HasDocumentedSubcommands() + { + await Task.CompletedTask; + var root = Nexo.CLI.Program.BuildRootCommand(); + var trust = root.Subcommands.Single(s => s.Name == "trust"); + var subcommands = trust.Subcommands.Select(s => s.Name).ToList(); + + // Named by README.md, docs/GettingStarted.md and scripts/security-gate-tier-c.sh. + subcommands.Should().Contain(new[] { "dashboard", "boundary", "pause", "resume", "pack" }); + trust.Subcommands.Single(s => s.Name == "pack").Subcommands.Select(s => s.Name) + .Should().Contain("apply"); + } +}