Skip to content

Derive every --settle surface from the descriptor trait (and gate the ones that can't) #1652

Description

@thymikee

postActionObservation in the command-descriptor registry is already the right enumerator for "which commands support --settle". The problem is coverage: only four of ten caller-facing surfaces derive from it, and the gate covers two. The other six are hand-spread per command and silently no-op when forgotten — a reader that drops settleInputFromFlags doesn't fail, it just ignores --settle.

This is the same failure mode commonInputFromFlags already documents for --no-record ("a drop at any one of them silently disables the flag"), and the same problem interaction-guarantees.ts already solved for dispatch paths by making completeness a compile error.

The drift is already in the tree

Cross-checking every surface against the trait (probe over commandDescriptors):

find         trait=false  cliFlag=false  mcpField=false  outSchema.settle=true
click        trait=true   cliFlag=true   mcpField=true   outSchema.settle=true
fill         trait=true   cliFlag=true   mcpField=true   outSchema.settle=true
longpress    trait=true   cliFlag=true   mcpField=true   outSchema.settle=true
press        trait=true   cliFlag=true   mcpField=true   outSchema.settle=true
back         trait=true   cliFlag=true   mcpField=true   outSchema.settle=true
scroll       trait=true   cliFlag=true   mcpField=true   outSchema.settle=false
  • find advertises a settle payload in its MCP output schema while carrying no trait, no CLI flag, and no MCP input field. It forwards req.flags to its internal press/click, so a hand-built daemon request can produce the payload — but no public surface can ask for it.
  • scroll declares nothing, purely because it isn't in the typed-result spine (CommandResultMap), not because it returns anything different from back. That one shipped in feat: support --settle on scroll and back (#1638) #1650 despite a gate and a careful review.

Surface coverage

Surface Derived? Hand-maintained sites
CLI allowed flags yes (trait)
MCP/SDK input fields yes (trait)
Timeout policy yes (trait)
MCP ref-pinning yes (trait, #1650)
CLI reader (settleInputFromFlags) no 6
Client-options projection (toSettleOptions) no 4
Contracts option types (& SettleCommandOptions) no 5
Session-event safe-flag specs no 8 lines / 4 specs
MCP output schema no 5
CLI output formatter no 4

Proposed work, in order

  1. Hoist the reader spread to the framework seam. readInputFromCli(command, positionals, flags) already knows the command name, so one line there deletes six hand-spreads and makes forgetting structurally impossible:
    return { ...reader(positionals, flags), ...settleInputForCommand(command, flags) };
  2. Derive the remaining three runtime surfaces — session-event safe-flag specs, MCP output schema, CLI output formatter. withSettleObservation (added in feat: support --settle on scroll and back (#1638) #1650 for back) already generalizes from "name one command" to "wrap every settle-capable command's schema".
  3. Extend the descriptor gate to all ten surfaces, in post-action-observation.test.ts. This is the item that actually matters: it converts six silent-drift surfaces into one failing test, and it would have caught both rows above. The type-level surfaces (contracts option types) can follow interaction-guarantees.ts and become a compile error rather than a runtime assertion.
  4. Resolve find — either give it the trait (it already forwards flags to its internal dispatch) or drop settle from its output schema. Needs a call on intent.

Explicitly out of scope

The two daemon orchestrations (interaction route vs generic route) look like the obvious duplication and are the wrong target. The interaction route settles inside the runtime command because it needs the resolution's pre-action baseline and composes with --verify; the generic route settles after dispatch against the stored session snapshot. Unifying them would mean inventing a fake resolution for scroll/back. The parts that genuinely are shared — the settle engine and issueSettleRefs — already are, as of #1650.

Sizing

Roughly −30 LOC. The line count is not the point: the reason to do this is that six surfaces currently fail silently, and the seventh settle-capable command will hit the same trap. Evidence that it is a real trap rather than a theoretical one: #1650 added two commands, touched seven surfaces by hand, had a gate, and still shipped the scroll output-schema asymmetry.

Follow-up to #1638 / #1650.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions