Skip to content

[MER-7781] add experimental decisions commands - #49

Merged
aviator-app[bot] merged 5 commits into
masterfrom
experimental-flag
Oct 29, 2026
Merged

aviator-app[bot] merged 5 commits into
masterfrom
experimental-flag

Conversation

@tr-heuser

@tr-heuser tr-heuser commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Adds aviator decisions for internal dogfooding. It ships in regular builds but is Hidden: true, so it's left out of --help, aviator help and shell completion. It runs when called by name.

Commands (/api/v1/verify/<n>/decisions)

  • list <id>: one page of a session's decisions in id order, with --status open|settled, --page and --limit. Shows each decision's comment_count; threads aren't included.
  • show <id> <decision-id>: one decision with its comment thread, raised_by (the user), author (the latest change's role and model), and resolution.
  • apply <id> --file <path|->: sends the file to POST /decisions as one all-or-nothing batch. A batch can create, change, settle, reopen and comment on decisions, and edit comments.

apply sends the file's bytes unchanged and leaves validation to the server's strict models. Decoding into Go structs and re-encoding dropped "summary": null, turned "id": 0 into a create, and silently dropped unknown keys inside anchor.

There are no per-verb write commands. The create, edit, comment, settle and reopen commands from earlier commits are removed, because each one rebuilt part of the server's rules on the client and got some wrong. For example, settle --summary turned a settle anyone can do into an edit only the raiser can.

--json builds its output from CLI-owned structs. The session is under id (matching dismiss), and list adds has_more and leaves comments out instead of printing [].

Commits

  1. Experimental build tag, removed again in commit 3.
  2. Decisions commands against the first version of the API.
  3. Hidden instead of tagged, plus a fix for anchor edits.
  4. Cut down to list, show and apply for the new API: single POST /decisions, comment edits in the batch, paged list with comment_count, external_bot role dropped, and the PATCH .../comments/<id> route no longer used.

Testing

  • Unit tests cover the list query and paging, that apply sends the file unchanged (nulls, "id": 0, unknown anchor keys), the --json shape, the text formatting, and that the command stays hidden.
  • The end-to-end run against a dev stack covered commits 2–3 only. list, show and apply haven't been run end to end against the new API yet.

Commands for internal dogfooding go behind the experimental Go build tag,
so no release (stable or nightly) includes them. config.Experimental gates
smaller changes inside existing commands. CI builds, tests, and smoke-tests
both variants, and golangci-lint runs with the tag on.
@aviator-app

aviator-app Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Current Aviator status

Aviator will automatically update this comment as the status of the PR changes.
Comment /aviator refresh to force Aviator to re-examine your PR (or learn about other /aviator commands).

This PR was merged using Aviator (commit 4a60283).


See the real-time status of this PR on the Aviator webapp.
Use the Aviator Chrome Extension to see the status of your PR within GitHub.

@tr-heuser
tr-heuser marked this pull request as ready for review October 23, 2026 19:10
@tr-heuser
tr-heuser requested a review from a team as a code owner October 23, 2026 19:10
@aviator-app

aviator-app Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

✅ FlexReview Status

Common Owner: aviator-co/engineering (expert-load-balance assignment)
Owner and Assignment:

  • ✅ aviator-co/engineering (expert-load-balance assignment)
    Owned Files
    • 🔒 CLAUDE.md
    • 🔒 cmd/aviator/decisions.go
    • 🔒 cmd/aviator/decisions_format.go
    • 🔒 cmd/aviator/decisions_test.go
    • 🔒 cmd/aviator/hidden_test.go
    • 🔒 internal/api/client.go
    • 🔒 internal/api/decisions.go
    • 🔒 internal/api/decisions_test.go

Review SLO: 7 business hours if PR size is <= 200 LOC for the first response.
❕ This PR modifies 901 lines, which is larger than the Review SLO threshold.

@aviator-app
aviator-app Bot requested a review from jainankit October 23, 2026 19:11
@tr-heuser tr-heuser changed the title add experimental actors and decisions commands [MER-7781] add experimental actors and decisions commands Oct 23, 2026
@jainankit
jainankit requested review from simsinght and tulioz and removed request for jainankit October 23, 2026 23:58

@tulioz tulioz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a lot of the size here comes from the API in aviator-co/mergeit#15148 splitting apart things that belong in one request, so the CLI rebuilds them on the client. i left comments there. once that's reshaped this can be one command per request, and it gets a lot smaller.

Comment thread internal/config/experimental.go Outdated

// Experimental reports whether this binary was built with the experimental
// tag. No release sets it; only local `just build-experimental` builds do.
const Experimental = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets drop the experimental build tag and just keep Hidden: true, which already keeps these out of help and completion, same as the hooks commands. the tag isn't free: CI and just test now run the whole suite twice, there's a separate build recipe and lint config, and since no release includes it nobody can dogfood from nightly, they have to build from source. this const has no callers either. if we want a convention for experiments, that should be its own discussion and not ride in with this feature.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment thread cmd/aviator/decisions.go
}

var decisionsApplyCmd = &cobra.Command{
Use: "apply <id>",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets drop apply and applyBatch and have each command make one request. applyBatch splits a batch into a create, a PATCH per decision and a comments POST per decision, with no transaction. say an agent applies a file with three decisions and the second PATCH gets a 409: the creates and the first patch are recorded, the rest aren't, and the only record of what landed is the error text. if it reruns the file, every create and comment gets posted again. every other command goes through applyBatch too, so the whole PR is shaped around this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API is now built around batching better, so this should be resolved in the new normal flow.

Comment thread internal/config/actors.go Outdated
"path/filepath"

"emperror.dev/errors"
"go.yaml.in/yaml/v3"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't want to add a direct yaml dependency just for this file. lets make it actors.json and use hujson like settingshook.go does for the claude settings, so we have one way of editing json config files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actors removed. No longer relevant.

Comment thread cmd/aviator/actors.go Outdated
"remote actors live in Aviator. You can also describe actors locally in\n" +
"~/.config/aviator/actors.yaml (or $XDG_CONFIG_HOME/aviator/actors.yaml),\n" +
"by hand or with `aviator actors edit`. The CLI never sends that file to\n" +
"the API; the server reconciles it with the remote actors.\n" +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the CLI never sends this file and nothing on the backend reconciles it, so this tells people something that doesn't happen. lets just say it's a local list of known names, and the server finds or creates actors by role and name on each write.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actors removed. No longer relevant.

Comment thread cmd/aviator/decisions.go Outdated
// resolveActors fills each actor's role from the local actors file, refusing
// any name that isn't there so a typo is caught before the batch is sent. The
// built-in person actor needs no entry and is sent without a name.
func resolveActors(refs []*api.ActorRef) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this refuses any name that isn't in the file. the file is there to steer agents toward names we already use, not to gate them, so a new name should go through as long as it comes with --role.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actors removed. No longer relevant.

Comment thread internal/api/decisions.go Outdated
// DecisionComment is one entry in a decision's thread.
type DecisionComment struct {
ID int `json:"id"`
Author DecisionAuthor `json:"author"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the backend sends this as actor, so this always decodes empty and show prints every comment with no author. the fixtures in decisions_test.go use author too, which is why the tests pass. it can also be null, same for raised_by.

Comment thread cmd/aviator/decisions.go Outdated

// buildAnchor turns the anchor flags into an anchor, or nil when none were
// passed.
func buildAnchor(path, lines, commit string) (*api.DecisionAnchor, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the backend replaces the anchor as a whole, so edit 7 --commit abc to bump the SHA also wipes the path and lines. lets merge the flags into the current anchor before patching.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment thread internal/api/decisions.go Outdated
type DecisionAuthor struct {
Role string `json:"role"`
Name string `json:"name,omitempty"`
AgentMeta *AgentMeta `json:"agent_meta,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the backend never sends agent_meta, so model in the --json output is always empty. lets drop it until the backend returns it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend sends this now, so it should not be empry anymore

aviator decisions list, show, create, edit, comment (and comment --edit),
settle, reopen, and apply record decisions on a Verify session's pull
request through POST /decisions/batch. Every command sends one batch, so
a change across several decisions is all or nothing; apply sends its JSON
file as the batch body. Each write is recorded under a --role (person,
coding_agent, review_agent, external_bot), with --model, --harness, and
--skill describing the agent when there is one. Hidden from help and
completion.
@tr-heuser tr-heuser changed the title [MER-7781] add experimental actors and decisions commands [MER-7781] add experimental decisions commands Oct 28, 2026
Drop the experimental build tag: decisions is Hidden, which keeps it out
of help and completion, so CI, just test, and lint run once again and no
separate build is needed. edit now lays --path, --lines, and --commit over
each decision's current anchor, since the server replaces an anchor as a
whole and a bare --commit used to wipe the path and lines. show and --json
leave out a null raised_by, author, or comment user instead of printing it
blank.
Comment thread cmd/aviator/decisions.go Outdated
Author authorFlags
}

var decisionsCreateCmd = &cobra.Command{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now that the backend takes one atomic batch, lets ship just list, show and apply, and drop create, edit, comment (including --edit), settle and reopen. yeah they're nicer to type, but each one rebuilds part of the backend's rules on the client and gets some wrong.

settle without --resolution always 400s with "Settling needs a resolution", and reopen without --body always 400s with "Reopening needs a comment saying why". settle --summary turns a settle anyone can do into an edit only the raiser can, so a reviewer settling someone else's decision gets a 403 and the whole batch is refused. comment edits should move into the batch too, i left that on aviator-co/mergeit#15404.

Comment thread cmd/aviator/decisions.go Outdated
// readDecisionBatch reads apply's file, which is the API's batch body. Unknown
// keys are refused, so a typo, or a via or batch-level model_metadata from the
// old format, is caught before anything is sent.
func readDecisionBatch(path string) (*api.DecisionBatchRequest, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply should send the file's bytes as the body unchanged and let the server's strict models validate it. decoding into Go structs and re-encoding drops things along the way. "summary": null or "" goes out as nothing, so clearing a summary comes back as "Nothing to change", and "id": 0 turns an edit into a create. unknown keys inside anchor also get dropped silently, because Nullable.UnmarshalJSON skips DisallowUnknownFields, so "anchor": {"file_path": "a.go", "line": 40} records an anchor with no line. with that and the commands above gone, Nullable, DecisionBatchItem, DecisionCommentInput and the old format tests can go too.

Comment thread cmd/aviator/decisions.go Outdated
"to all of them, and apply records a whole JSON batch. Each command sends\n" +
"one request, so either all of it is recorded or none of it is.\n" +
"\n" +
"Writes act as the caller, so they need a user access token. Comments are\n" +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets drop this, nothing a user can see shows where a comment came from.

Comment thread CLAUDE.md Outdated
a personal token to `api`.
- `PATCH /<id>/comments/<comment_id>` — `{body}`.
- Writes need a user token on an account with Verify. Only the user who
raised a decision can change it (403); anyone in the account can comment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anyone can settle or reopen, only changes to type, title, summary and anchor are limited to the raiser. this should also say settling needs a resolution and reopening needs a comment. the api.Nullable and via lines above are CLI internals, lets keep this block about the size of the other endpoints.

Comment thread cmd/aviator/decisions_format.go Outdated
}

type decisionListJSON struct {
Session string `json:"session"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets call this id like dismiss does. session is a new key for the same thing.

The backend now takes one atomic POST /decisions batch, so the per-verb
commands (create, edit, comment, settle, reopen) are gone: each rebuilt part
of the server's rules client-side and got some wrong. apply sends the file's
bytes unchanged and lets the server's strict models validate them, so nulls,
zero ids, and unknown keys are no longer dropped in a Go round trip. Comment
edits move into the batch.

list follows the paged API with --status, --page, and --limit and reads
comment_count. The JSON list key is id instead of session, and list output
leaves comments out rather than printing [].
@aviator-app
aviator-app Bot merged commit 4a60283 into master Oct 29, 2026
4 checks passed
@aviator-app
aviator-app Bot deleted the experimental-flag branch October 29, 2026 01:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants