Information Command Taxonomy Decision#
Status#
Accepted and implemented. The cleanup (retire explain solution, delete dead
explain provider, remove empty get authhandler/, relocate get resolver refs -> eval refs) and the inspect solution --usage view landed together.
Problem Statement#
scafctl accumulated several overlapping commands for “tell me about X”. Auditing the surface revealed genuine duplication and a few misfiled commands:
solutionis overloaded across four verbs:get solution– lists the catalog / shows a light summary.explain solution– fixed-text dump of the solution’s structure.inspect solution– kvx-rendered view of the same structure, plus the inferred run command.diff solution– pairwise structural comparison.
explain solutionandinspect solutionbuild the same data object (pkg/solution/inspect.BuildSolutionExplanation->SolutionExplanation) and differ only in renderer (fixedWritertext vs. the kvxOutputOptionspipeline).inspect solution’s own help text already states it supersedesexplain solution. This is historical duplication.explain provideris dead code: it is defined (pkg/cmd/scafctl/explain/provider.go) but deliberately never wired into the command tree, because “explain provider” (a provider instance) collides with “explain provider” (the schema-browser lookup of the provider kind). At runtime,explain providerresolves to the schema view, not the instance – so the instance command is unreachable.get resolver refsis mis-verbed: it extracts resolver references from a template/expression string. It neither lists nor shows a “resolver”, so it does not belong under thegetverb.get authhandler/is empty dead code (no Go source).
The net effect: a user asking “how do I understand/consume a solution?” faces
three similar commands (get, explain, inspect), none of which is clearly
the user-facing “how do I run this?” view (the gap tracked by issue #300).
Prior Art#
Best-in-class CLIs separate these concerns into distinct verbs:
- kubectl:
explain TYPE= schema/field docs for a kind;describe NAME= detailed state of an instance;get= list/retrieve instances. - docker:
image inspect/container inspect= instance structure. - helm:
helm show values/chart/readme <chart>= user-facing “how do I use this chart”;helm get= instance state.
The consistent pattern is: one verb for schema-of-a-kind, one for instance detail, one for listing. scafctl already half-follows this – the cleanup makes it consistent.
Decision#
Adopt a three-verb taxonomy for information/discovery commands, separated by what they operate on:
| Verb | Meaning | Operates on | Output |
|---|---|---|---|
get <kind> [name] | list many, or show one by name | a kind (list) or one instance (summary) | kvx |
explain <kind>[.field] | schema documentation for a kind | a kind (Go-struct-tag schema) | kvx (target) |
inspect <resource> | full structure/metadata of a specific instance | an instance | kvx |
Rules that follow from this split:
explainmeans “schema of a kind” only. Remove the instance misfits: retireexplain solutionand delete the deadexplain provider. After this,explain <kind>has exactly one meaning and no naming collision.inspectis the instance view. It absorbs whatexplain solutiondid (kvx-native, already the reference implementation). The user-facing “usage” view for issue #300 is a projection ofinspect(e.g. aninspect solution --usageview or aninspectusage projection), not a new top-level command. This avoids inventing a new convention and avoids repurposingexplain.getstays “list a kind, or show one by name.” It keeps the catalog listing role. Relocateget resolver refsout ofget(it is an extraction/eval operation, not a get). Remove the emptyget authhandler.
Why the #300 usage view lives under inspect#
inspect’s charter is already “inspect the structure and metadata of resources with full kvx output support.” A user-facing usage view is inspecting a solution’s metadata + parameters + actions so it can be consumed correctly – squarely within the existing intent, not a repurpose.- A dedicated top-level
usageverb would apply only to solutions today, which is too narrow to justify a new convention. Theinspectprojection pattern generalizes to other resources later. explain(schema) stays untouched and unambiguous.
Consequences#
Removed / relocated (fewer commands, not more):
explain solution– retired (its role isinspect solution). Breaking; a deprecation alias may bridge one release (see Sequencing).explain provider(instance) – deleted as dead/unreachable code.get authhandler/– removed (empty).get resolver refs– relocated to a verb that matches its behavior (extraction), e.g. underevalor aresolvergroup.
Added:
- A user-facing usage projection under
inspectfor issue #300 (synopsis, parameters with defaults/allowed values, per-action run commands), rendered via the kvx pipeline so table/json/yaml/TUI come for free.
Unchanged:
getlisting/summary behavior and catalog integration.explain <kind>schema browser for kinds.eval(expression sandbox) andsnapshot(execution artifacts) – confirmed distinct, out of scope.diff solution– distinct pairwise comparison.
Sequencing#
This decision spans more than issue #300. Suggested split into separate PRs:
- Taxonomy cleanup (its own issue/PR): delete dead
explain providerand emptyget authhandler; relocateget resolver refs; retire (or deprecate -> alias)explain solutionin favor ofinspect solution. Migrate docs, examples, and tests. Updatedocs/design/cli.md. - #300 usage view (layered on the cleaned base): add the
inspectusage projection with the data-building logic inpkg/solution/inspect(extendBuildRunCommand), an optionalmetadata.usageenrichment block, and graceful fallback forwhen-clause value discovery.
Breaking changes are acceptable in this pre-production project; note them explicitly in the PR titles/bodies (squash-merge repo).