Shell facet
Table of Contents
A model's shell commands are codegen output. The ores.cpp.shell-command facet
emits one command unit per entity, junction or operation model: a submenu named
for the model's plural, and one command per verb or per declared message. The
unit lands in the adapter part named for the model's component.
Return to Knowledge.
1. The facet
| Field | Value |
|---|---|
| Facet | ores.cpp.shell-command |
| Model type | domain_entity, junction, operation |
| Level | cross |
| Default | disabled |
| Archetypes | command_header, command_implementation, command_tests, operation_header, operation_implementation, operation_tests |
Each model owns its own three files. Nothing is aggregated into one file, so a unit can be added or removed without touching its neighbours.
2. How a model opts in
The facet is off unless something turns it on, and three things can:
- The facet node's own
#+default:. It isdisabledtoday. - A variability profile's
* Physical spacetable. A row| =ores.cpp.shell-command| true |= turns the facet on for every model bound to that profile. Seven profiles fix it:fk-scoped-child,fully-featured-lookup,self-referencing-hierarchy,simple-lookup,tenant-scoped-junction,uuid-identified-lookupanduuid-surrogate-lookup. - The model's own properties drawer,
:ores.cpp.shell-command.enabled: true.
A model whose profile already fixes the facet states nothing itself. The drawer
entry exists for the models that bind no profile, which is every operation model
and the entities that carry no :profile: at all. Every entry in the shell
surface generation story was checked against this rule, and the thirteen entries
a profile already covered were removed.
3. What withholds a unit
Two gates run before the opt-in is read, so neither can be overridden by a drawer entry:
- A staging shim whose plural another component also models renders no unit. The
shell names its menu after the plural, so two owners of one plural would
answer the same menu, and the shell resolves a duplicate by registration
order. dq's
report_definitionshim is withheld from reporting'sreport_definitionsmenu this way. - A model that suppresses
ores.cpp.protocolrenders no unit, because the unit includes the derived request types that do not exist without it.
4. The entity and junction unit
The submenu is named for the model's plural. The verbs are the ones the shape derives:
| Verb | Shape |
|---|---|
list |
One page, with --offset, --limit, --order and --desc. |
get |
One row by key. |
get-many |
Rows by a list of keys. |
add |
A create. |
set |
A replace, with --version. |
put-many |
A batch upsert. |
delete |
A delete, with --version. |
delete-many |
A batch delete. |
versions |
The version history, paged. |
version |
One version, with --version. |
A junction derives the same set, and a parent-scoped child adds its
by-<parent>-id read. A current_state entity has no version rows, so its unit
carries no versions or version.
The add verb takes free-form tokens rather than one typed parameter per column. The cli library expands a typed handler into one template instantiation per argument, and the Windows clang toolchain abandons the mangler on the deeper chains. A token vector holds the argument list at a fixed shape.
5. The operation unit
An operation model states messages, so its unit carries one command per message
that declares a subject and a response. The command name is the message name
without its _request suffix, kebab-cased, unless the message states a
shell_command of its own.
The body sends the request and prints the reply through its JSON codec. An operation unit therefore reads as JSON where a hand-written command may have printed rows.
A request field with a :default: arrives as a --<name> flag and the struct's
own initialiser stands when the caller omits it. A field without one is a
positional argument, so a request model that means a filter to be optional
states a default for it.
6. Who supplies a column's value
The add verb fills one domain member per positional argument. The
:supplied_by: annotation on a column says who supplies the value.
| Value | Meaning |
|---|---|
user |
The default. The verb reads the value from the next positional. |
minted |
The verb mints a fresh uuid. The default for a uuid primary key. |
session_party |
The verb reads the acting party from the session, and fails when the account has no default party. |
unset |
The verb leaves the member at its default and asks for nothing. |
The tenant_id member takes no annotation. It is not a declared column, so the
template names it directly and fills it from the session.
7. File locations
The unit belongs to the adapter part named for its component, not to
ores.shell.application.
| File | Path |
|---|---|
| Header | projects/ores.shell/{component}/include/ores.shell/app/commands/{component}/{entity}_commands.hpp |
| Implementation | projects/ores.shell/{component}/src/app/commands/{component}/{entity}_commands.cpp |
| Tests | projects/ores.shell/{component}/tests/{entity}_commands_tests.cpp |
An operation's three files are named {operation}_operations_commands.
Every consumer includes the header as
ores.shell/app/commands/{component}/{entity}_commands.hpp, which is its path
under the part's own include/ directory. The part declares
#+component_kind: adapter, so codegen writes only its two file lists beside
the hand-authored CMakeLists.
8. Registration
One aggregator per component wires the units to the shell host. It is
hand-written and lives in the application part at
projects/ores.shell/application/src/app/commands/{component}/{component}_commands.cpp.
It includes each unit's header and calls register_commands once per unit.
repl::setup_menus calls the aggregator once at startup.
The facet has no component-scoped archetype for that aggregator, so adding a
model means one include and one call in the aggregator, plus a refresh of the
part's file lists. A generated aggregator needs a render-time list of the
component's opted-in models, the same pattern the generator already uses for
_COMPONENT_FILES_TEMPLATES.
The aggregator registers no command of its own, which is why it is not counted as a hand-written command unit.
9. The units replace, they do not join
Two units must never register one submenu. The shell resolves a duplicate name by registration order, so the first unit answers every verb the two share and the second unit's verbs answer without appearing in the help.
Delete the hand-written unit for a capability in the same change that generates its replacement. A hand-written verb that a generated one supersedes is a defect, not a compatibility layer. Where a hand-written command stays, it is because the model cannot state what it does: it changes this client's own session, it dispatches a workflow and waits on it, it composes several responses into a view no model declares, or it moves a file.
10. Enabling the facet by default
The facet's default is the last lever, and it is not yet safe to pull. Flipping
#+default: to enabled would render a unit for every model of every component,
not only the components that have been cleaned.
Measured on 2026-10-01 at 4eec0e6740: 76 models across 16 components would
gain a unit. Nine of them bind one of three profiles that do not fix the facet
(readonly-timeseries, table-less-record, trading-instrument); the other 67
carry no :profile: at all and are operations or entities whose component has
not finished its clean-standard pass.
The precondition is therefore that every component reaches the clean standard first. Until then, a model opts in from the profile that covers it, or from its own drawer when no profile does.
11. See also
- ores.cpp.shell-command — the facet, its archetypes and its routing table.
- compass-codegen-add-surface-entity — skill that drives this workflow.
- How do I create shell commands for a new entity? — recipe walkthrough.
- Entity lifecycle — layer ordering overview.