← Files CorezoidARCHIVED FILE
skills/corezoid-edit-bot/references/change_kinds.md
10.9 KB · Oct 10, 2026 · 06:12 UTC
# Change kinds
One row per shape `corezoid-edit-bot` supports. `Touches` is what the change is allowed
to modify; anything outside that list is a different change kind. `Proves it` is
an **observed** result, never a clean lint.
Section references are to `invariants.md`.
| Kind | Touches | Verification that proves it |
|---|---|---|
| `add-command` | a new process in the bots folder (`create-process` → `pull-process` → write → push), a new alias, new Localization keys, a new Attachments entry, `mainKeyboard` + `mainMenu` | `run-task` on the Router with the new command reaches the bot (not `Command not found`); Send Message renders each new `text_id` with every `{{var}}` resolved; a dialog advances on the follow-up `modify` |
| `remove-command` | `pause-process` or `delete-process`, drop the `mainKeyboard` button and the `mainMenu` mention; **leave** the Localization keys | whatever the removed command now *actually* does, observed — the alias survives, so `Command not found` is a hypothesis, not a guarantee (below); every other command still reaches its bot |
| `rename-command` | process title, **a new** alias, button payloads, `mainMenu` text, PLAN.md | the new command reaches the bot; the old command's behaviour reported as observed, since its alias also survives (below) |
| `copy` | the Localization task — **plus every process that sends the affected `text_id`, if the new wording adds a `{{placeholder}}`** (§1) | `show-task ref: localization` shows the new text in every language the wizard seeded; `run-task` on Send Message renders it with no literal `{{var}}` left |
| `keyboard` | one Attachments task; plus the sending node if the attachment becomes dynamic (it then needs `items`/`currentPage` forwarded, §1) | `show-task ref: <attachment_id>` shows one key per channel in `channels` (`facebook`, not `fbmessenger`); `run-task` on Send Message returns the expected `reply_markup`; a live client renders it |
| `add-locale` | the Localization task — **every** key gains the new language — and PLAN.md `locales` | no key is missing the new language; a User Profile with `language: <new>` gets the new copy |
| `dialog-step` | one command's process (ask → wait → keep → validate block), its Localization keys, maybe its Attachments entry | the dialog advances through every step and the domain call receives each answer in its own argument — not the latest message |
| `wire-process` | one command's process (a new `api_rpc`/`api_copy`/state-read block, **plus namespacing on both calls** if it is now the 2nd, §7), its Localization keys, the send node's `data`, the PLAN.md coverage row | `run-task` on the command returns the new process's payload keys, the existing call's verdict is still read correctly, and Send Message renders the new values |
| `process-remap` | one command's `api_rpc` `extra`/`extra_type`, its outcome-mapping Code node, and the send node's `data` if the payload keys changed | `run-task` with a known input returns the expected rendered text, and every alternate outcome still maps to its own `text_id` |
| `bugfix` | the smallest set of nodes that reproduces the symptom | the failing repro now passes at the layer it failed at (`repair_loop.md`), plus one command that was already working |
| `template-edit` | a wizard-owned process (Main, Router, Send Message, System Diagram, a Messengers process) | **guarded** — see below |
| `deploy` | nothing locally; promotes one stage onto another | `deploy-stage` dry-run diff reviewed, then applied with the confirm token, then the full smoke test re-run **against the target** |
## The alias is write-once, which reshapes three of those rows
`create-alias` is the only alias tool in the plugin — there is no delete,
modify, unlink or list counterpart (§8). Consequences an edit must state rather
than paper over:
- **`remove-command`.** `delete-process` moves the process to the recycle bin
(restorable from the UI) and `pause-process` leaves it in place refusing new
tasks — but **neither frees the alias**, so the Router keeps resolving
`@<command>` to it. What the user actually sees is therefore an open question:
observe it. If the name must stop resolving, that is raw Corezoid API work —
hand it to `corezoid:corezoid-alias-manager` with the user's agreement, and
until then describe the command as still dispatching.
- **`rename-command`.** The new name needs a **new** alias. The old alias stays
and keeps pointing at the same (now retitled) process, so the old command
keeps working. Report that; do not claim the old command is gone.
- **`add-command`.** Check the candidate against `_ALIASES_.json` before writing
CHANGE.md, while the name is still just a string. Discovering a collision
after the `text_id`s, button labels, menu entries and process title are
written means a rename that cascades through all of them — and an
`obj_to_id: null` row holds the name while resolving to nothing.
`pause-process` is the reversible option and it is a dry-run by default: to
apply, pass `apply: true` with `confirm: "process#<id>:<live_status>->paused"`.
It is admission control, not proof that tasks already parked in nodes stopped —
a user mid-dialog keeps their session.
## `copy` and `keyboard` are not always task-only
They are the two kinds that look cheapest and are the easiest to get silently
wrong, because Send Message is called with `group: ""` and receives only the
fields the calling node names in `data` (§1):
- New wording that introduces `{{someValue}}` requires that key in **every**
sending node's `data`. Find the senders with
`grep -rln '"text_id": *"<id>"'` over the orchestrator; a node sending the
literal `{{text_id}}` may carry any value a Code node assigns, so union them.
- An attachment that becomes dynamic requires `items` **and** `currentPage`
forwarded, and `items: ""` (not `[]`) on the no-rows path.
- A placeholder for a key the process never produces renders as an empty string
— only keys in that process's `success.keys` are safe.
And a **shared** key (`mainMenu`, `mainKeyboard`, `serviceError`, `timeout`,
`commandNotFound`, `selectError`, `carouselPattern`) has a `template-edit`-sized
blast radius even though it touches no process: set `regression_scope: all` and
smoke-test every command.
## Not supported — say so rather than improvising
- **Adding a channel to an existing orchestrator.** `create-communications-orchestrator`
is not incremental: it builds a whole folder, including the per-channel
receiver and API-method processes under `Messengers/`. There is no way to
graft a channel the wizard did not build. Putting a token into the `Tokens`
config produces a channel with no receiver and no send path — a bot that
looks configured and answers nothing. The honest answer is a **new**
orchestrator built with every channel, then re-pointing the webhooks and
retiring the old folder; that is a `corezoid-gen-bot` run, not an edit.
- **Removing a channel.** Same reason. What you *can* do is stop registering
its webhook, which leaves the processes idle.
- **Deleting, repointing or renaming an alias.** No MCP tool does it (§8).
`corezoid:corezoid-alias-manager` documents the raw API calls.
- **Contract drift in a domain process.** If a handed-in process changed — lost
a reply node, got paused, gained an alternate outcome, renamed an input — run
`/corezoid-gen-bot refresh` first so PLAN.md and `bot-contract.json` reflect
reality, then come back with a `process-remap` or `dialog-step` change.
Editing the call against a stale contract is how a command starts sending an
empty `extra` key or mapping a normal outcome to `serviceError`.
- **Editing a handed-in domain process.** They are someone else's system and the
bot is a caller, not an owner. If one genuinely has to change, say so and hand
it to `corezoid:corezoid-edit` with the user's agreement — then
`/corezoid-gen-bot refresh` to pick up the new contract.
- **A second orchestrator "just to try something".** It steals the webhook from
the first one, and there is no undo.
## Three invariants every domain-call change must preserve
All silent when broken — nothing fails at push time, the bot just misbehaves in
a chat. Full versions in `invariants.md`.
1. **A `time` semaphore on every `api_rpc` into a domain process** (§6), routed
to a node that tells the user and then copies `/end` into the Router.
Without it a paused, hung or reply-less callee parks the task forever: no
message, and the chat's `System Diagram` state stays `active`, so the user's
next message goes into the dead command too. Lint enforces no floor on it
(it's a timeout, not a hold) — 30 s is a reasonable default, not a required
minimum.
2. **Namespacing once a command makes two or more calls** (§7). `res_data`
merges into the caller's task at top level and every process in this family
replies with the same `{result, code}` envelope, so the second callee
overwrites the first's verdict. Follow every call with an `api_code` — not a
`set_param`, which lint fails as `UNUSED SET_PARAM` because it cannot see a
`data.result` read inside JavaScript. Adding a second call is therefore a
change to **both** calls, not an addition to the end of the graph.
3. **Forwarding whatever the new payload feeds into the message** (§1). A remap
that changes which keys the callee returns changes the send node's `data`
too; otherwise the old key renders as an empty string and the new one as a
literal `{{var}}`.
Also: **never probe or smoke-test a `likely`/`unknown` side effect without the
user's agreement.** A `run-task` against a real domain process can send a real
SMS or charge a real card. Agree a safe input, or exercise the dialog only up to
the confirm step.
## `template-edit` is guarded
The wizard's own processes are shared by every command and by all four
channels, and the alias dispatch means a new command never needs them touched.
So a `template-edit` is only legitimate when the change is genuinely global —
a new field on every outbound message, a channel-wide send fix — and then:
1. Name the exact process and the exact nodes in CHANGE.md, with why no
command-level change achieves it.
2. Get the user's explicit confirmation of that named process. A bare "go" is
not consent to edit Router.
3. `create-snapshot` before the push. `push-process` auto-snapshots an existing
process anyway, but a titled manual checkpoint is the one thing here whose
loss cannot be regenerated from PLAN.md.
4. Push without `force`, without `overwrite_server_change`, without
`allow_no_snapshot`.
5. Smoke-test **every** command afterwards, not just the one that motivated the
change — that is what "shared by every command" means.
A `push-process` on a template process reporting a concurrent server change
means someone or something else is editing this orchestrator. Stop and ask; do
not overwrite. `push-process merge=true` writes a reviewable 3-way merge plus a
`.pre-merge` backup and deploys nothing — that is the tool for reading what
changed under you.
SHA-256: 31b59b9304b0bf69df5c86cec50d2f1c70672b3c6b60d58e51f3b51b61b2a065