> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cordango.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Semantic operations

> Structural change through cordango apply.

A semantic operation is a described change rather than an edited file. You hand `cordango apply` a
list of operations and it rewrites the affected source files.

```bash theme={null}
cordango apply ops.json --app support --scope domain
cordango apply - --app support --scope screen:tickets    # ops on stdin
```

This is the preferred path for structural change, and the path an agent should use.

## Scope is required

`--scope` names the one aggregate the change may touch.

| Kind                 | What it covers                       |
| -------------------- | ------------------------------------ |
| `identity`           | The app's own identity and metadata. |
| `domain`             | Entities, fields and relations.      |
| `behaviour`          | Lifecycles, actions and automations. |
| `access`             | Roles and grants.                    |
| `screen:<key>`       | One screen.                          |
| `tab:<screen>/<tab>` | One tab of one screen.               |

An operation naming anything outside the scope is refused, and **no file is written**. A rejected
attempt costs nothing and needs no cleanup, which is what makes it safe to let an agent try.

## The operations

<Tabs>
  <Tab title="domain">
    `upsert_entity`, `upsert_field`, `remove`
  </Tab>

  <Tab title="behaviour">
    `upsert_lifecycle`, `upsert_action`, `upsert_automation`, `upsert_role`, `remove_behaviour`
  </Tab>

  <Tab title="ui">
    `upsert_screen`, `remove_screen`, `upsert_screen_tab`, `remove_screen_tab`
  </Tab>
</Tabs>

Upserts, deliberately. An operation that describes the state a thing should be in can be applied
twice without a different result, which matters when the thing applying it is retrying.

## Ask what an operation may say

```bash theme={null}
cordango vocabulary operation
cordango vocabulary operation upsert_field
```

The operation vocabulary is a different question from the App Definition's, and for a while it was
the one nobody could ask. An agent worked it out by submitting deliberately invalid operations and
reading the rejections. The schemas existed the whole time; nothing exposed them.

<Warning>
  An operation and a file spell some things differently. A reference's target is `target` in an
  operation and `targetEntity` in a file; a trigger is `on` against `trigger`. `cordango vocabulary`
  lists every pair. See [the schema](/concepts/schema).
</Warning>

## The file

```json theme={null}
{
  "note": "Add a priority field to tickets",
  "ops": [
    {
      "op": "upsert_field",
      "entity": "ticket",
      "field": {
        "key": "priority",
        "type": "select",
        "label": "Priority",
        "default": "normal",
        "options": [
          { "value": "low", "label": "Low" },
          { "value": "normal", "label": "Normal" },
          { "value": "high", "label": "High" },
          { "value": "urgent", "label": "Urgent", "color": "#B4232A" }
        ]
      }
    }
  ]
}
```

`note` is for the human reading the diff later.

## See it before you do it

```bash theme={null}
cordango apply ops.json --app support --scope domain --dry-run
```

Prints which files a change **would** write or delete, and writes nothing.

## Then check

```bash theme={null}
cordango check --app support
git diff
```

`cordango apply` writes to the working tree. It does not stage, does not commit, and does not
validate on your behalf. A person reads the diff and decides.

## Why not just edit the file

For a one-line change, do edit the file. Operations earn their place in three situations:

**Scope enforcement.** The guarantee that a change cannot touch anything outside one aggregate is
worth having when the thing making the change is a model.

**Refusal is free.** A rejected operation set leaves the tree exactly as it was. There is no
half-applied state to clean up.

**They describe intent.** `upsert_field` on `ticket` is a reviewable statement about what was
meant. A YAML diff shows what changed but not why it was one change.
