docs(adr): clarify native CLI errors and compatibility
This commit is contained in:
1 parent
23dfb6d956
commit
1b7565113c
1 file changed
+13
-8
+13
-8
@@ -23,7 +23,7 @@ implemented_by: []
|
||||
|
||||
[Issue 8](https://gitea.auvem.com/corestack/go/issues/8) needs command-specific resource setup, signal-aware work, cleanup on completion, automatic help and distinct usage failures. The owner selected `github.com/urfave/cli/v3` v3.14.0 for every command, subcommand, flag and help surface; callers declare native flags. One-offs skip module services; serve runs them. The root-module boundary and lifecycle ordering, cancellation and shared shutdown budget already stand. This clean-room proposal uses no go-toolkit/appcli source.
|
||||
|
||||
The public API, adapter-owned signal subscription and exact classification boundary below are proposed for independent review and owner acceptance, not approved implementation requirements. No additional owner decision blocks drafting; acceptance must include the documented native validation limitation.
|
||||
All new decisions below remain proposed pending owner acceptance: the three-function API, adapter-owned signal subscription, compatibility limits and exit/diagnostic exceptions. The approved native dependency and one-off service suppression remain settled; this proposal authorizes no implementation.
|
||||
|
||||
## Considered Options
|
||||
|
||||
@@ -51,22 +51,27 @@ func Action(config lifecycle.Config, modules []lifecycle.Module,
|
||||
|
||||
The functions capture module declarations in their supplied order; callers list every needed module explicitly. There is no registry, name lookup, implicit dependency expansion or DAG. Callbacks close over caller-owned resource handles populated by Setup; the one-off receives the selected native command for parsed flag access. There is no public App, worker method, configuration framework or flag/environment facade. Empty module lists are valid. Nil one-off functions, nil Run context/root, invalid lifecycle config and invalid selected modules are runtime errors before resource acquisition; validate every selected declaration before starting setup. Bindings do not start work until invoked. A command tree and its mutable resources have one execution owner; construct fresh trees for independent runs and do not mutate captured declarations concurrently.
|
||||
|
||||
`Serve` constructs a fresh lifecycle App, registers selected modules and calls its Run. `Action` copies selected modules with Run cleared, then registers an internal final module with no-op Setup/Teardown and the command function as Run. All selected setups precede work; returning from the one-off leaves no selected services to keep the App alive. Lifecycle closes admission, cancels the callback context and performs reverse cleanup. No success sentinel, cancellation-error filtering, parent-context cancellation or second shutdown timer is added. Action failures are lifecycle failures and preserve their chains. Core setup failure/partial cleanup, parent cancellation, second signal, abandonment and joined-error behavior remain authoritative. Selected module services cannot run during one-offs. New background work in these CLI callbacks is not offered a managed-worker API; raw goroutines remain caller-owned and cannot claim lifecycle's managed-worker completion guarantee.
|
||||
`Serve` constructs a fresh lifecycle App, registers selected modules and calls its Run. `Action` copies selected modules with Run cleared, then registers an internal final module with no-op Setup/Teardown and the command function as Run. Choose its nonempty name by checking all selected names and varying a private suffix until unused; no public module name is reserved. All selected setups precede work; returning from the one-off leaves no selected services to keep the App alive. Lifecycle closes admission, cancels the callback context and performs reverse cleanup. No success sentinel, cancellation-error filtering, parent-context cancellation or second shutdown timer is added. Action failures are lifecycle failures and preserve their chains. Core setup failure/partial cleanup, parent cancellation, second signal, abandonment and joined-error behavior remain authoritative. Selected module services cannot run during one-offs. The invocation App is private: modules or callbacks that need its App.Go cannot obtain it through these adapters and are incompatible with this narrow API. Capturing another App does not confer this invocation's supervision. Supported modules manage resources and optional services without that access; raw goroutines remain caller-owned and outside managed-worker completion. Exposing worker access would require a separate owner decision.
|
||||
|
||||
Each invoked adapter subscribes to SIGINT/SIGTERM using a private channel buffered for two signals when Config.Signals is nil, and defers signal.Stop. With a supplied channel, subscription, filtering and cleanup remain caller-owned as in lifecycle; a closed supplied channel disables signals. The adapter never closes either channel. No subscription or relay goroutine is needed during parsing/help. Lifecycle alone consumes/counts signals and cancels command work; do not also use signal.NotifyContext, which would turn graceful Ctrl-C into parent cancellation. After adapter return, subscription cleanup restores the prior process signal behavior; the caller owns process exit and abandoned resources.
|
||||
|
||||
Run owns the tree's ExitErrHandler, OnUsageError and CommandNotFound hooks, rejecting conflicting caller hooks as runtime configuration errors rather than silently overriding them. It installs a non-exiting root handler and per-command usage/not-found handling before urfave runs; no urfave package globals are changed. Native actions, Before/After, ArgValidator, flags, arguments and help templates remain caller declarations. Hooks outside the lifecycle action are outside its signal/shutdown/resource guarantee: use them for prompt validation, and put resource work inside selected modules or the adapted action. Leave group Actions unset for native group help/unknown-command handling; positional arguments on actionable leaves follow native urfave declarations and validators.
|
||||
Run owns the tree's ExitErrHandler, OnUsageError and CommandNotFound hooks, rejecting conflicting caller hooks as runtime configuration errors rather than silently overriding them. It installs a non-exiting root handler and usage/not-found handling on the caller-declared tree before urfave runs; no urfave package globals are changed. CommandNotFound returns void, so its callback records a local usage cause for this invocation; Run combines that cause with the native result even when urfave returns nil, retaining any simultaneous runtime error. Native actions, Before/After, ArgValidator, flags, arguments and help templates remain caller declarations. Hooks outside the lifecycle action are outside its signal/shutdown/resource guarantee: use them for prompt validation, and put resource work inside selected modules or the adapted action. Leave group Actions unset for native group help/unknown-command handling; positional arguments on actionable leaves follow native urfave declarations and validators.
|
||||
|
||||
The exact proposed mapping is:
|
||||
|
||||
| Result | Code |
|
||||
| --- | --- |
|
||||
| Nil error, including help, empty group help, normal completion or graceful first signal with successful cleanup | 0 |
|
||||
| No returned or locally recorded error, including valid help, empty group help, normal completion or graceful first signal with successful cleanup | 0 |
|
||||
| Lifecycle setup/action/service/teardown error, parent cancellation, budget/second-signal abandonment, or other unclassified native error | 1 |
|
||||
| Native OnUsageError path (invalid/missing flag value, required flag/argument, mutually exclusive flags, native argument parsing), or unknown command/help topic | 2 |
|
||||
| Native OnUsageError path on caller-declared commands (invalid/missing flag value, required flag/argument, mutually exclusive flags, native argument parsing), or locally recorded unknown command/help topic | 2 |
|
||||
| Parse failure in a urfave-generated help command, including its aliases | 1 |
|
||||
| Explicit `urfave.Exit(err, 2)` from pre-lifecycle caller validation | 2 |
|
||||
|
||||
A lifecycle failure always remains code 1, including action-returned ExitCoder values and joins containing cleanup failures; do not infer success by matching context.Canceled. Non-2 ExitCoder values outside lifecycle normalize to 1; an error never becomes success through ExitCode 0. Use private wrapping classification that retains causes, and let runtime failures dominate mixed usage/runtime failures. Run returns the original/wrapped error for errors.Is/As inspection after cleanup, and emits one final diagnostic to root.ErrWriter. Usage hooks also request the relevant native help through urfave's Show*Help functions. Native help goes to root.Writer and diagnostics to root.ErrWriter, defaulting to stdout/stderr; callers may supply writers there. No logger/writer registry or logging policy is added. Rendering retains urfave's own writer-error behavior, without promising stronger delivery guarantees.
|
||||
A lifecycle failure always remains code 1, including action-returned ExitCoder values and joins containing cleanup failures; do not infer success by matching context.Canceled. Non-2 ExitCoder values outside lifecycle normalize to 1; an error never becomes success through ExitCode 0. Use private provenance wrappers for usage and lifecycle failures. Native After hooks may aggregate failures in urfave.MultiError, whose Errors() children are invisible to standard traversal. A small private bridge traverses both standard Unwrap forms and native MultiError.Errors(), recursively exposing children through Unwrap() []error while also retaining each original error/wrapper as a traversal child. It delegates Error() to the original rather than duplicating its text. Do not flatten or replace native wrappers: errors.Is must still match their identity and errors.As must still find their types, alongside nested causes and private markers.
|
||||
|
||||
Classify every semantic branch, carrying its usage/lifecycle provenance through wrappers and aggregates; aggregate containers are not extra unclassified failures. Any lifecycle branch or unclassified runtime branch dominates usage branches, including an Exit(err, 2) action plus failing After, usage plus runtime After, and nested parent/leaf After failures. Looking up only the first ExitCoder or marker is insufficient. Run returns the traversable error after cleanup and emits at most one adapter-owned final diagnostic to root.ErrWriter. This is not a single total diagnostic guarantee: native help parse handling and caller hooks may also print. Usage hooks request relevant native help through urfave's Show*Help functions. Native help goes to root.Writer and diagnostics to root.ErrWriter, defaulting to stdout/stderr; callers may supply writers there. No logger/writer registry or logging policy is added. Rendering retains urfave's own writer-error behavior, without promising stronger delivery guarantees.
|
||||
|
||||
Proposed owner exception: urfave creates generated help nodes lazily after the pre-run hook walk; their parse failures bypass OnUsageError. Accept code 1 with native diagnostics for malformed generated help invocations (root, nested and aliases); Run's final diagnostic may repeat the native error. Valid help remains code 0, and unknown help topics remain code 2 through the recorded CommandNotFound cause. Uniform code 2 and one total diagnostic would require separate feasibility evidence and owner approval. Do not initialize by reparsing, copy native parser/help code, or detect diagnostics through error strings.
|
||||
|
||||
Urfave v3.14.0 does not expose all validation errors through OnUsageError: invalid environment values in Flag.PostParse bypass it, as do ArgValidator and flag-action errors. Unclassified errors from those paths are code 1. Callers needing code 2 from custom validators return `urfave.Exit(err, 2)` before lifecycle entry; native environment-source failures remain code 1 unless the caller validates explicitly. Do not inspect error strings, reparse arguments, wrap every flag type or promise universal parse/runtime discrimination.
|
||||
|
||||
@@ -93,6 +98,6 @@ The dependency belongs in root go.mod/go.sum under the accepted boundary ADR. At
|
||||
|
||||
* Scope is one PR, approximately 200–350 production lines plus focused tests and docs: new lifecycle/cli/cli.go (package/public comments and adapters), cli_test.go, example_test.go, root go.mod/go.sum and this ADR. No existing callers, fixtures, lifecycle core, migration package, release/config framework or GUI changes are anticipated. The reusable migration command belongs exclusively to issue 9.
|
||||
* There are no persistence writers, migrations, remote services, new auth boundaries or persistent workers. Active work consists of lifecycle-owned callbacks, its existing shutdown deadline, and a temporary OS signal subscription only while an adapted action executes. Callers own command output, cooperative callbacks, partial setup cleanup and eventual process exit; importing the package starts nothing.
|
||||
* Tests must prove selected-only setup and registration/reverse order, no one-off services, teardown on success/failure, serve services, fixed code mapping with preserved typed causes and mixed cleanup errors, native flags/subcommands/help without module setup, invalid environment classification, injected first/second signals, parent cancellation and the shared deadline. Test with injected channels and explicitly release blocked goroutines; do not send real signals. Exercise native callbacks returning ExitCoder without process exit.
|
||||
* Raw feasibility on main f043900: an external Go experiment with v3.14.0 produced `setup action teardown`, nil error for normal one-off and injected SIGINT, without core edits. Help invoked no action; unknown command returned native code-3 error; bad/required flags invoked OnUsageError; invalid environment IntFlag returned an error with zero usage-hook calls. The pinned [command handler](https://github.com/urfave/cli/blob/v3.14.0/command.go) bypasses default process exit with root ExitErrHandler; [Exit(error,2)](https://github.com/urfave/cli/blob/v3.14.0/errors.go) unwraps its cause. The [execution source](https://github.com/urfave/cli/blob/v3.14.0/command_run.go) places PostParse outside usage handling. These are feasibility observations, not implementation tests or independent review.
|
||||
* Tests must prove selected-only setup and registration/reverse order, no one-off services, teardown on success/failure, serve services, fixed code mapping with preserved typed causes and mixed cleanup errors, native flags/subcommands/help without module setup, invalid environment classification, injected first/second signals, parent cancellation and the shared deadline. Test with injected channels and explicitly release blocked goroutines; do not send real signals. Exercise native callbacks returning ExitCoder without process exit. Add focused action/lifecycle-plus-After, usage-plus-runtime-After and nested After tests asserting code 1, original wrapper identity/types and all child causes. Cover root/nested generated help parse errors and aliases (code 1/native diagnostics), valid help (code 0), unknown commands/help topics returning native nil (code 2), and synthetic-name collisions without reserving names.
|
||||
* Native [aggregation and parse handling](https://github.com/urfave/cli/blob/v3.14.0/command_run.go), [MultiError API](https://github.com/urfave/cli/blob/v3.14.0/errors.go) and [lazy help setup](https://github.com/urfave/cli/blob/v3.14.0/command_setup.go) establish integration obligations; they do not verify the unimplemented adapters. Retain these cases in upgrade tests. The simpler alternative is narrower hook support; retaining native hooks with a small traversal bridge preserves the requested cause guarantees. Accepting the generated-help exception avoids a second parser/help subsystem.
|
||||
* Review and owner acceptance precede implementation. Later standard and fresh high independent implementation reviews, repository checks and reconciliation against the latest target branch precede publication; this draft records no acceptance, production delivery, PR or CI success.
|
||||
Reference in new issue
Block a user