# Changelog

## [1.8.0]

### Added
- **Account tags & triggers tools + skill coverage.** Exposed the `account_to_actors` mechanism — the backend's way of attaching actors to accounts — as curated tools. **`saveAccountActors`** (POST `/papi/1.0/accounts/actors/{nameId}/{currencyId}`) links **Tags**-form actors (account tags) or **AccountTriggers**-form actors (balance / transaction-count triggers) to a workspace `(name, currency)` pair or — via `accountId` — to one specific account; the call REPLACES the linked set, so the tool description mandates scoping with `formId` (an unscoped pair-level call wipes the pair's tags AND pair-level triggers alike; only per-account bindings survive). **`getDataFieldActorsByActor` / `saveDataFieldActorsByActor` / `getDataFieldActorsByForm` / `saveDataFieldActorsByForm`** manage DATA triggers — `AccountTriggers` actors with `valueType=data` + `operator`/`comparisonValue` bound to a data field of one actor or of every actor of a form (the two same-named `formId` slots on the form route are disambiguated via `Param.Wire` as `triggerFormId`). Extended **`getAccounts`** with the backend's `tag` (filter by tag-actor UUID), `ungrouped`, `withTags`, and `withTriggers` params. **Backend dependency (pong-server, same change):** declared the five missing `operationId`s on the public `/papi/1.0/accounts/actors/*` routes and re-dumped `papi-openapi.json` so the drift gate passes. **Skill:** new "Account tags & triggers" section in `simulator-finance` (system-form discovery via `getForms(formTypes="system")`, the `AccountTriggers` data-field schema incl. the `[{title,value}]` select shape, zone-transition firing semantics — `lowerZone`/`normalZone`/`upperZone`, fires only on zone change — webhook delivery to users with access to the trigger actor, the replace-semantics warning, and a `dataField`-is-a-field-id note), plus tag/trigger activation phrases in the frontmatter and a matching section in `docs/entities/accounts.md`. Added four eval scenarios + dry fixtures for the new tools. Updated the README / `docs/ARCHITECTURE.md` tool tables (new "Account tags & triggers" row; also added the previously-unlisted `createAccountPair` to the Accounts row) and the curated-tool figure to ~100. Bumped manifests to 1.8.0.

## [1.7.1]

### Added
- **Open-source community files.** Added a top-level `LICENSE` (MIT — previously only declared in the plugin manifest), `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, and a `.github/` directory with issue templates (bug / feature + config), a pull-request template, and a `dependabot.yml` (gomod + github-actions, weekly). Added status badges and a "Contributing & support" section to the README. Bumped all manifests to 1.7.1.

## [Unreleased]

### Added
- **AI reaction agent — reaction-tool & docs sync.** Documented the platform's AI reaction agent on the reaction surface. `createReaction` / `updateReaction` now describe **`extra.mcp`** (set `true` to hand a reaction to the agent — it processes the reaction under the requesting user's access via this MCP server and posts its answer back as a child **`ai`** reaction) and gained a **`reasoning`** body param (`{inProgress, thoughts:[{id,text,createdAt}]}` — the agent's streamed progress/answer trace). Expanded the `simulator-reactions` skill (new "AI agent reactions" section, with a don't-loop note) and `docs/entities/reactions.md` (interaction-scheme diagram, field table, the `ai` type). Tool-surface descriptions/params only — no new operations; drift gate unaffected.
- **`getCorezoidProcesses` tool (v1.10.0).** Lists the Corezoid processes available to an actor — the ones shared to it via its access API keys — answering "what functions/processes can this actor call?". Public route `GET /papi/1.0/actors/corezoid_processes/{actorId}` already carried the `operationId`; re-dumped `papi-openapi.json` to include it, added the curated tool (`actors.go`), an eval scenario, and the README / `docs/ARCHITECTURE.md` Actors row.
- **Behavioural eval hardened to green in both dry and live (v1.10.0).** Drove the behavioural eval (`cmd/evalrunner`) from 88/103 dry + 84/108 live to **~103/103 dry** and all previously-failing live scenarios passing. Runner gains: **any-of tool groups** (`"a|b"` in a scenario's `tools`, satisfied if either is called — for interchangeable tools like `getTransfer|getTransferByRef`, `getRelatedActors|getLinkedActors`); **`dryOnlyTools`** + **`argCheck.dryOnly`** (assert the follow-up step of a real-dependency flow only in dry, where fixtures let the model reach it — live scores the reachable primary call, since a placeholder-id 404 correctly stops the chain); and **dry fixtures for the new tools** (users, system-actor, attachments, form-accounts/tree, reactions, counters, transfers…) so multi-step dry scenarios resolve. Diagnosed every failure as a *test/environment* artifact (overlap-strictness, missing fixture, underspecified or ellipsis-`id` prompt, or live chain-abort on non-existent entities) — **zero skill/tool defects**. Fixed the scenarios accordingly (concrete ids/refs, full UUIDs, `getUsers`-based discovery, directive prompts) and corrected one genuinely-wrong scenario (`getAllLayerPlacements` is layer→actors, not actor→layers). `eval_test.go` validates the new `dryOnlyTools` / any-of syntax. Documented the knobs in `AGENTS.md`.
- **Form-account & form-tree tools (v1.10.0).** Exposed the form-level account model and UAT navigation. **`createFormAccount` / `getFormAccounts` / `removeFormAccount`** define **default accounts on a form template** that the backend auto-creates on every actor of that form (and removes from all of them on delete) — the clean way to shape a whole form's account model. **`getFormsTree` / `getLinkedForms`** navigate the form tree (UAT: root + descendants / direct parents+children). **Backend (pong-server):** added `operationId`s on the public `/papi/1.0/form_accounts/*` routes, **created a new public `forms_graph` router** (`getFormsTree`/`getLinkedForms`, previously internal-only) and wired it into `apiRegister`, added missing `tags` to the form-account/form-graph schemas, and **fixed a schema/handler mismatch** in `createFormAccountReq` (the public schema documents `accountType` but the handler only read `type` → always defaulted to `fact`; it now accepts `accountType`, falling back to `type`). Re-dumped `papi-openapi.json`. Rewrote the stale "accounts attach to actors, not the form" section of `simulator-forms` (now: form-level default accounts vs ad-hoc per-actor) and updated the form-tree notes in `simulator-forms`/`simulator-actors` (traversal is now curated). Added eval scenarios + README / `docs/ARCHITECTURE.md` Forms row.
- **`getSystemActor` tool + user-to-user transfer workflow (v1.10.0).** Added `getSystemActor` (public `GET /papi/1.0/actors/system/{accId}/{objType}/{objId}`, `objType="user"`) — resolves a user's system "twin" actor by `userId`, the actor that carries that user's accounts. **Backend dependency (pong-server):** declared the `operationId` and re-dumped `papi-openapi.json`. Documented the full **money-between-users** workflow in `simulator-finance`: `searchUsers` (find by name) → `getSystemActor` (twin actor) → `getAccounts` (its accountId) → `createTransfer`. Also added the **rule that any movement of value between two+ accounts — different actors OR two accounts of one actor — must use the transfer tools** (`createTransfer`/`createTransferTwoStep`), never `createTransaction` (single-account). Wired `getSystemActor` into `simulator-actors`, the README/ARCHITECTURE Actors row, and eval scenarios (resolve-twin + two-user transfer flow).
- **Workspace-members (users) tools (v1.10.0).** Added `getUsers`, `getUser`, `searchUsers` (public `/papi/1.0/users/*`) so the access-rule/sharing tools have a way to resolve a `userId` / `groupId` grantee — previously there was no curated way to list or find workspace members. **Backend dependency (pong-server, same change):** declared the `operationId`s on the three users routes and re-dumped `papi-openapi.json`. Wired into `simulator-access` (new "Finding the grantee" section) and added a Users row to the README / `docs/ARCHITECTURE.md` tool tables + eval scenarios (incl. a search-then-share flow).
- **Three new specialist skills + finance/graph skill updates (v1.10.0).** Added skills for the new tool domains that no existing skill covered: **`simulator-reactions`** (comments/events/approvals/ratings on actors — threaded; the `actorId` (parent) vs `reactionId` (own) protocol), **`simulator-attachments`** (files: `uploadBase64` → `attachId` → `addAttachments`; raw-base64 note; `uploadActorPicture` for avatars), and **`simulator-access`** (access rules: privs/recursive/notify model, async `taskId`, bulk + account-pairs). **Rewrote `simulator-finance` end-to-end** to the real v2 tool surface: replaced every stale slug-style example (`post-accounts-actorId`, `get-currencies-accId`, …) and wrong arg shapes (`description`→`comment`, `title`/`decimals`→`name`/`precision`, `fromAccountId`/`toAccountId`→`from`/`to` leg arrays, `transactionId`→`ref`-based finalize, dropped the non-curated formula/bulk-ref calls and the bogus `boolean` account type), added the new tools (`getAccount`/`setAccountAmount`/`getChildAccounts`/`search*`/`updateAccountName`/`atomCreateTransaction`/`createTransferTwoStep`/`filterTransfers`/`*ByRef`), a **Counters** section (Scylla-backed, history-less analytics/anti-fraud tallies, distinct from the `counter` account *type*), trilingual activation triggers, and cross-links to `simulator-actors`/`simulator-access`/`simulator-charts`; updated **`simulator-graph`**'s tool list with the new traversal/layer tools (`getActorLinks`, `getLinkedActors`, `layerStats`, `existLayerElement`, `moveActors`, `cleanGraphLayer`); and listed the new skills in the universal `simulator` skill router. Regenerated `public/` discovery artifacts; bumped skill count 7 → 11 and the curated-tool figure to ~85 in README / AGENTS / `docs/ARCHITECTURE.md` (also added the previously-unlisted `simulator-charts` to the README skill table).
- **Account, counter & transfer/transaction tools — full finance surface (v1.10.0).** Closed the remaining gaps in the accounts API. **Accounts/currencies/names:** `getAccount` (one account by id), `setAccountAmount` (fixed-balance correction), `getChildAccounts`, `updateAccountName`, `searchCurrencies`, `searchAccountNames`. **Counters** (new domain — per-event values keyed by actorRef+accountName, e.g. mileage): `saveCounters`, `setCounters`, `getCounters`. **Transactions/transfers:** `atomCreateTransaction` (all-or-nothing batch), `getAccountTransactions` (by account id), `getTransactionByRef`, `createTransferTwoStep` (authorize→complete, mirrors finalizeTransaction), `filterTransfers`, `getTransferByRef`. **Backend dependency (pong-server, same change):** declared the missing `operationId`s on the public `/papi/1.0/accounts/*`, `/account_names/*`, `/currencies/search/*`, `/counters/*`, `/transactions/*`, `/transfers/*` routes (adding `tags` to the three search/update schemas that lacked them) and re-dumped `papi-openapi.json`. Skipped niche/system routes (formula, block, dataField-actors, inherit, streams_unread, ref-variants, triggers `/end`). Added eval scenarios + README / `docs/ARCHITECTURE.md` tool tables (new Counters row).
- **Graph-traversal & layer-management tools (v1.10.0).** Filled the remaining gaps in the graph surface: **`getActorLinks`** (every edge of an actor), **`getLinkedActors`** (directly-linked actors with edge-type/system filters; complements `getRelatedActors`' hierarchy directions), **`layerStats`** (node/edge counts for a layer), **`existLayerElement`** (is a node/edge on a layer — dedup before placing), **`moveActors`** (move ≤10 actors between layers), and **`cleanGraphLayer`** (wipe a layer; destructive). **Backend dependency (pong-server, same change):** declared the missing `operationId`s on the public `/papi/1.0/graph/actor_links/{actorId}`, `/graph/linked_actors/{actorId}`, `/graph_layers/exist/{actorId}`, `/graph_layers/move/{src}/{tgt}`, and `/graph_layers/clean/{actorId}` routes (`layerStats` already had one) and re-dumped `papi-openapi.json`. `getActorLinks` / `getLinkedActors` take the standard `filter` field-projection param. Added eval scenarios + README / `docs/ARCHITECTURE.md` tool tables.
- **Reactions, attachments & file-upload tools (v1.10.0).** Added the comment/event, file-link, and upload surfaces that were public on the backend but had no tools. **Reactions** (events/comments living under an actor; the path `{actorId}` is the parent actor, a reaction's own id rides in the body): **`createReaction`** (type=comment for a note; threads via `parentId`), **`updateReaction`**, **`deleteReaction`**, **`getReactions`** (tree/flat/thread), **`getReactionsStats`**, **`markReactionsRead`**, **`getPinnedReactions`**, **`togglePinnedReaction`**. **Attachments:** **`getAttachments`**, **`addAttachments`** / **`removeAttachments`** (link/unlink files to actors/reactions; root-array body of `{attachId,actorId}`), **`updateAttachment`** (rename). **Upload:** **`uploadBase64`** (single-JSON file upload → returns the attachment record incl. `attachId`; multipart stays with the `uploadActorPicture` engine tool). Skipped the system/no-auth endpoints (`ds_callback`, `upload/proxy`, `upload/callback`, `attachments/files`) and the niche `ai_action`. Added a **`Param.Wire`** field to the generic handler so an MCP arg name can differ from its wire name — needed because reactions reuse `actorId` for both the path (root actor) and the body (the reaction's own id), now exposed as distinct `actorId` / `reactionId` args (covered by `TestWireDecouplesArgFromBodyKey`); `createReaction`'s default-true `notify` uses the existing `KeepFalse`. **Backend dependency (pong-server, same change):** declared the missing `operationId`s on the public `/papi/1.0/reactions/*`, `/attachments/*`, and `/upload/base64/{accId}` routes and re-dumped `papi-openapi.json`; also broadened `uploadBase64`'s data-URI strip from `^data:image/\w+;base64,` to `^data:[^;]+;base64,` so non-image files sent as a `data:` URI are no longer uploaded corrupted. Added eval scenarios for each tool + the README / `docs/ARCHITECTURE.md` tool tables.
- **Access-rule (permissions) tools (v1.10.0).** The curated set had no way to manage who can see or change an object; added the full `access_rules` surface — **`getAccessRules`** / **`saveAccessRules`** (generic, `objType` ∈ actor/form/account/templateActors/formTemplate/treeLayer), the template-actor and tree-layer variants (**`getTemplateActorsAccess`** / **`saveTemplateActorsAccess`** / **`getTreeLayerAccess`** / **`saveTreeLayerAccess`**), **`bulkSaveAccessRules`** (≤50 objects), and **`bulkSaveAccountPairsAccessRules`** (share a whole account-name category across the workspace). Saves take a root JSON array of `{action:create|update|delete, data:{userId|saId|groupId, privs:{view,modify,remove,sign?,ds?,execute?}, reactionOrders?}}` and are applied asynchronously (return a `taskId`). Added a `Param.KeepFalse` flag to the generic handler (`op.go`) so the **default-true** query flags `recursive`/`notify` send an explicit `false` instead of being dropped by the omit-when-false convention (which is correct only for default-false flags) — without it, "grant access but don't cascade to children" / "…quietly" would be silently ignored and the backend's true default would win. Covered by `TestKeepFalseQueryFlagSentWhenFalse`. **Backend dependency (pong-server, same change):** declared the missing `operationId`s on every public `/papi/1.0/access_rules/*` route, and **promoted the previously internal-only `bulk/accounts_pairs/{accId}` route to the public API** (`bulkSaveAccountPairsAccessRules`, gated by `actors.management` + `checkAccAccess`); re-dumped `papi-openapi.json` via `yarn dump-openapi` so the drift gate passes. Added eval scenarios for the read/grant/revoke/bulk-share paths, and documented the tools in the README / `docs/ARCHITECTURE.md` tool tables. Bumped all manifests 1.9.0 → 1.10.0.
- **Full link (edge) CRUD tools.** The curated set previously covered only link *creation* (`createLink`, `massLink`). Added the rest of the lifecycle: **`getEdge`** (read by id), **`updateEdge`** (label/linkedActor/curveStyle/pinned), **`deleteEdge`** (by id), **`existLink`** (lookup/dedupe by `(source,target,edgeTypeId)`), and **`deleteEdgesByNodes`** (bulk delete by endpoints). Enriched `createLink`/`massLink` with the `linkedActorId` and `pinned` fields the backend already accepted. **Backend dependency (pong-server, same change):** declared the missing `operationId`s on the public `/papi/1.0/actors/link/{edgeId}` (GET/PUT/DELETE), `/actors/exist_link`, and `/actors/bulk/actors_link` routes (`getEdge`/`updateEdge`/`deleteEdge`/`existLink`/`deleteEdgesByNodes`), added `bidirected` to the exist/bulk-delete body schema and `pinned` to the update-link body schema, and re-dumped `papi-openapi.json` via `yarn dump-openapi` so the drift gate passes. Added eval scenarios + dry fixtures for each new tool, and documented them in `docs/entities/links.md`, the README / `docs/ARCHITECTURE.md` tool tables.
- **Form-template & actor `data` protocol, fully documented + a new `simulator-actors` skill (v1.9.0).** Captured the real form/actor wire contract that the docs and the forms skill were getting wrong:
  - **Forms = "Account Templates" / «Шаблон рахунків».** Surfaced this product alias in `createForm`'s summary, `docs/entities/forms.md`, the `simulator-forms` skill, and the README.
  - **`docs/entities/forms.md`** now carries the authoritative field-class catalogue (`edit` + type text/password/email/phone/int/float, `check`, `radio`, `select`, `multiSelect`, `calendar`, `upload`, `label`/`button`/`image`), all 8 dynamic `select` `optionsSource` types (layer / actorFilter / actorsBag / actors / currencies / accountNames / workspaceMembers / corezoidSyncApi), and a worked `sections ↔ data` example. The decisive rule — **actor `data` is keyed by each field's `id` (`item_<digits>`), not its title or `key`** — and the per-class value shapes are documented in `docs/entities/actors.md`.
  - **MCP tool descriptions** for `createForm`/`updateForm` (`sections`) and `createActor`/`updateActor` (`data`) now embed the catalogue + value-shape rules (`sectionsDesc`/`actorDataDesc` in `forms.go`/`actors.go`) so the model emits valid payloads. Description-only — the drift gate `(method, path, operationId)` is untouched.
  - **New `simulator-actors` skill** (7 skills total) — actor-instance lifecycle (`createActor`/`getActor`/`getActorByRef`/`updateActor`/`setActorStatus`/`deleteActor`/`searchActors`/`filterActors`/`searchLayerActors`) focused on the `data` value protocol, distinct from `simulator-graph` (graph STRUCTURE).
  - **Rewrote the `simulator-forms` skill**: replaced the fictional `data.fields` model with the real `sections`/`content`/`item_*` protocol, and **fixed broken tool references** — the old account workflow called `post-accounts-pair-accId` / `post-form_accounts-formId`, which don't exist in the v2 curated set; accounts attach to **actors** via `createAccount`, so the workflow now uses `createAccountName`/`createCurrency`/`createAccount`.
  - **Multiform actors + form trees (UAT).** Documented that an actor can instantiate several forms at once and that its `data` namespaces fields by owning form via `KEY_FORM_PREFIX` — `__form__<formId>:<itemId>` (root-form fields stay plain `item_<id>`; e.g. `{"name":"…","__form__16951:position":"…"}`). Added this to `actorDataDesc` (`actors.go`), `docs/entities/actors.md`, `docs/entities/forms.md` (new "Form trees and multiform actors (UAT)" section + `parentId` linking), and the `simulator-actors` / `simulator-forms` skills. Completed the `optionsSource` catalogue with the remaining `FORM_OPT_SOURCE` values (`manual`, `forms`, `formFilter`, `api`) and the `form` item-type (value = form id) across the same files. The form-tree traversal (`forms_graph`) and form-set attach (`PUT /actors/actor_forms`) endpoints are internal / lack a public `operationId`, so they are **not** added as curated tools (would fail the drift gate) — multiform `data` is fully writable today via `createActor`/`updateActor`/`getActor`.
  - **Behavioural eval: full tool coverage + argument assertions + skill injection.** Overhauled the eval harness (`cmd/evalrunner` + `eval-scenarios.json`):
    - **`argChecks`** on scenarios — assert on the model's *arguments*, not just which tools it called: `mustContain` / `mustNotContain` (regression guards) / `mustMatch` (regexp), matched over each tool's canonical-compact args. E.g. `createCurrency`/`createAccountName` must carry `"name"` and must **not** carry `title`/`decimals` (guards the param-name bug fixed this release); `createActor` multiform data must carry `__form__16951:item_position`; dynamic-select must carry `{"type":"workspaceMembers","value":1}`.
    - **Dry-mode fixtures** — read/list tools now return small canned payloads (a "Vehicle" form 42 with known `item_*` fields, an actor, USD/Km currencies, …) instead of `{}`, so dry scenarios can chain (e.g. `getForm` → `createActor` with real ids) and be scored on argument shape **without a backend**.
    - **`--skills` mode** (`make eval-skills`) — injects the plugin `SKILL.md` files as the Anthropic `system` prompt, approximating how a host loads skills. Closes the prior gap where the eval validated only tool *descriptions*, never the skill prose.
    - **Full coverage** — expanded from 11 to **49 scenarios**, at least one per registered curated/engine tool (forms, actors, accounts/finance, graph, engine, search, workspace). The structural test (`eval_test.go`) enforces every scenario/`argChecks` tool is real and listed; `AGENTS.md`'s "Adding a curated MCP tool" checklist now requires a scenario.
  - **CI (`.github/workflows/ci.yml`).** New required `gate` job on push/PR: `make build`/`vet`/`test` + **discovery-freshness check** (`make discovery` then `git diff --exit-code -- public/`) + advisory `make lint`. A separate `behavioural-eval` job runs `make eval` on `workflow_dispatch` only, using the `ANTHROPIC_API_KEY` secret (so it never spends credits on ordinary pushes).
  - **Graph-sync unit tests.** Expanded `internal/engines/graphsync_test.go` to cover previously-untested seams: `injectMassLinkData`, `injectManageLayerData`, `fetchHierarchyEdgeTypeID` (+ caching), `updateGraphActor` diff decision, `fetchLayerActors` pagination, and `buildBaseURL` precedence. Updated the now-stale "no dedicated unit tests" notes in `AGENTS.md` / `CLAUDE.md`.
  - **Rewrote `docs/user-flows/custom-car-form.md`** to the v2 model: accounts attach to **actors** (`createCurrency`/`createAccountName`/`createAccount`), removed the fictional form-embedded-accounts model and the non-existent `accounts/pair` tool, and fixed the broken `entities/currencies.md` link → `accounts.md`.
  - **English-only skill instructions.** Replaced the hardcoded Russian "which workspace?" prompt in the `simulator` / `simulator-forms` / `simulator-actors` / `simulator-finance` skills with an English instruction that tells Claude to ask **in the user's own language** (en/uk/ru) — instruction language is now English everywhere, while user-facing replies stay multilingual. Neutralized a Russian example value in the actor-`data` samples. Kept the multilingual *activation triggers* in the `description:` frontmatter (they improve ru/uk auto-activation) and the Ukrainian product alias «Шаблон рахунків» (the literal UI label). Codified this as a **"Skill language" convention** in `AGENTS.md` (+ a cross-reference in `CLAUDE.md`): English-first instructions, reply in the user's language, with activation triggers and product terms as the deliberate multilingual exceptions.
  - Bumped all manifests 1.8.0 → 1.9.0; updated README / AGENTS / ARCHITECTURE skill listings & counts (and dropped the stale `software-migration-onramp` line from the ARCHITECTURE diagram). Regenerated `public/` discovery artifacts.
- **CDU Page Protocol reference.** New `plugins/simulator/docs/user-flows/cdu-page-protocol.md` documents the Smart Form runtime page contract derived from the "Simulator.Company Scripts" OpenAPI, the `control-cdu` renderer, and the pong-server serving/save path: the `get`/`send` transport + response codes (200/205/302) + realtime, the `Page → grid → forms → sections → items` model, the full component catalogue (28 classes), templating (`viewModel`/`locale`/`definitions` `$ref`/`contentLoop`/`bbcode`), the `changes` patch protocol (incl. `changeRules` array actions), client-side validation, and — critically — **what pong-server validates vs. stores opaque when saving page files** (envelope only; `config`/`viewModel`/`locale`/CSS are not structurally validated). Cross-linked from `smart-forms.md` (§4.3 save-validation note, §4.7 serving) and the user-flows README.
- **Smart Forms public-API parity + user-flow doc.** Documented the full Smart Forms / applications (CDU) lifecycle in `plugins/simulator/docs/user-flows/smart-forms.md` (data model, catalog → install → edit → deploy/release → rollback → serve, access model, public vs internal API, per-endpoint scope table). Refreshed `internal/tools/testdata/papi-openapi.json` via pong-server's `yarn dump-openapi`: the public API now exposes the previously internal-only `releases` and `file_history` modules and the missing `applications` / `app_content` endpoints (`getApplication`, `getApplicationEnvs`, `updateApplicationEnv`, `createAppContent`, `getAppContentFolder`, `deleteAppContent`, `listReleases`, `getRelease`, `getReleaseStruct`, `diffReleases`, `rollbackRelease`, `listFileVersions`, `getFileVersion`, `rollbackFile`, `listAppTrash`, `restoreAppContent`) — reads gated by `actors.readonly`, writes by `actors.management`, all mirroring the internal `checkAccess` rules. **Depends on the matching pong-server backend change (new `publicApi/applications/*` routes) — not yet merged, so this spec leads the deployed backend.**

### Fixed
- **Account `accountType` enum was wrong — `createAccount` (and the finance skill) used a fictional asset/liability/expense/income set (v1.10.0).** The backend keys an account's `accountType` to **`FORM_VALUE_ACCOUNT_TYPES` = fact/plan/min/max/avg** (default `fact`) and selects counters via a **separate `counterType`** field (amount/counter/uniqCounter/systemCounter); there is no asset/liability/income enum (the account NAME carries that meaning). The Go `accounts.go` `accountTypes` advertised `asset/liability/expense/income/counter/state` for the `accountType` arg/path of `createAccount`/`updateAccount`/`deleteAccount`/`getAccounts`/`getChildAccounts`/`getTransactions` — so any model that followed the tool's enum (e.g. `accountType="asset"`) got a **400** from the backend, and counter accounts were uncreatable (no `counterType` param). Fixed the enum to `fact/plan/min/max/avg`, added a `counterType` param to `createAccount`, corrected the descriptions, and **rewrote the account-type model across `simulator-finance` / `simulator-forms` / `simulator` skills** (removed the fictional asset/liability framing; documented accountType=value-type + counterType + name-carries-meaning). Also aligned `createFormAccount`'s `formId` param type with its siblings and bumped the stale "~85 tools" architecture figure to ~95. Caught by `/code-review`.

### Changed
- **Applications / Smart Forms MCP tools deferred (docs-only stage).** Deregistered the `appOps` domain (`createApplication`, `createSmartForm`, `listSmartForms`, `manageAppContent`) from the curated tool set — at this stage only the Smart Forms documentation + public spec ship, not the tools. The `appOps` definitions stay in `internal/tools/apps.go` (referenced but excluded from `allOps()`), ready to re-enable. Removed the now-stale apps entries from the scenario test, eval scenarios, evalrunner cleanup switch, and the MCP-tools tables in README / `docs/ARCHITECTURE.md`.

- **Server-side field selection (`filter`) on every read tool — token-saving.** All read/lookup/list tools now expose an optional `filter` query parameter: a comma-separated allow-list of fields to return (e.g. `id,title,data.status`; dotted paths pick nested `data` fields). The backend prunes the response to just those fields via `filterActorData` / `filterData`, so a tool call can fetch only what it needs instead of the full entity. Centralised as `fieldFilterParam` in `internal/tools/op.go`.
  - Tools whose backend already supported `filter`: added it to `getActor`, `getBalance`, `getTransactions`, `searchActors`, `searchLayerActors`, `filterActors`, and **fixed** the two pre-existing `filter` params on `getAccounts` and `getRelatedActors` whose descriptions wrongly called them data-filter expressions (the backend always treats `filter` as field selection).
  - **Added `filter` support in pong-server** for the remaining read routes and wired the matching tools: `getActorByRef`, `getForm`, `getForms`, `searchForms`, `getCurrencies`, `getAccountNames`, `getTransfer`, `getEdgeTypes`, `getLayerActors`, `listSmartForms`, `searchAll`, `getWorkspaces`. Each backend route declares `filter` on its Fastify querystring schema and applies `filterActorData` (actor/node responses: `getActorByRef`, `getLayerActors` nodes, `searchAll` actor results — user results untouched, `objType` preserved) or `filterData` (everything else); `workspaces` clones session-held objects before projection.
  - The drift gate (`(method, path, operationId)` only) still passes; refresh `testdata/papi-openapi.json` via pong-server's `yarn dump-openapi` once the backend changes deploy so the dumped spec carries the new params. Documented in README / `docs/ARCHITECTURE.md`.

### Removed
- **`software-migration-onramp` skill.** Dropped the migration discovery facilitator skill (and its `prompts/` specs); the plugin now ships 6 skills. Removed its references from README / `docs/ARCHITECTURE.md` / AGENTS / the `simulator` skill and regenerated the discovery artifacts (`public/`).

### Added
- **Local dev preset is now profile-gated.** `set-environment` offers the `local` preset (`localhost:9000`) only in a local-dev session (startup profile `local`, i.e. `SIMULATOR_PROFILE=local` / `--profile local`); the default `prod` profile advertises only the cloud gateways (`mw`, `sim`) + custom URL, so end users are never prompted with localhost. Documented the local-dev invocation in README / AGENTS, and dropped the stale `SIMULATOR_DEBUG` references (it was never read in code).
- **Multi-environment support + `set-environment` tool.** Before login the user now chooses an environment — a cloud preset (`mw.simulator.company` default, `sim.simulator.company`) or a custom/local/on-prem URL (host or full URL; `/papi/1.0` appended if omitted). Since pong-server authenticates through the `account` system and one account may back several sim environments, the OAuth account URL is no longer fixed per gateway: `set-environment` fetches the gateway's public, unauthenticated config (`getConfigReq` → `data.saUrl`) to derive it, persists `SIMULATOR_API_BASE_URL` + `ACCOUNT_URL` to `.env`, and clears any token + workspace (so you re-`login`). The environment can be switched at any time mid-session — the `apiclient` and engine base URLs are mutated in place (guarded by `RWMutex`) so it takes effect without a restart, and `login` follows the chosen account URL. New `apiclient.FetchPublicConfig`; `config.CloudEnvironments` presets + `NormalizeAPIBaseURL`; `config.Resolve` honours a persisted `ACCOUNT_URL` on restart. Documented in README / `docs/ARCHITECTURE.md` and the `simulator-init` / `simulator` skills.
- **`filterActors` tool** (`GET /papi/1.0/actors_filters/{formId}`) — list/rank the actors of a form by an account's **current balance** in a single server-side query. Set `linkedToActorId` to scope candidates to one anchor actor's graph neighbours (hierarchy link, both directions), answering "the actors related to X whose account N balance is > / < a value"; `accountNameId`+`currencyId` select the account, `amountFrom`/`amountTo` set the threshold, `orderBy=balance` ranks. Filters on current balance only (turnover-over-period stays a per-actor `getAccounts(from,to)` read; the backend's period-balance path is still disabled). Declared the matching `operationId: 'filterActors'` on the pong-server public route and refreshed the drift spec. Re-introduces, in curated Go form, the `filterActors` tool dropped in the rewrite. Documented in README / `docs/ARCHITECTURE.md` and the `simulator-finance` / `simulator-graph` skills.

### MCP server rewrite
- Replaced the monolithic `app/mcp-server/server.go` Swagger→MCP bridge with a **layered server** under `cmd/server/` + `internal/`: `config` (local/prod profiles, env/`profiles.json`-overridable), `apiclient` (auth header + accId injection + timeouts + error mapping, workspace guarded by `RWMutex`), `tools` (a curated, typed `Operation` registry → one MCP tool per backend operation, declared per domain), and `engines` (the ported client-side tools). OAuth/credentials reused from `app/auth`.
- **Curated tool set (~46 tools)** scoped to the core scenarios — forms / actors / accounts / transactions / graph (links & layers) / applications & smart forms — plus the engine tools `pullGraphFile`, `pushGraphFile`, `getAllLayerPlacements`, `compactGraphLayout`, `pruneLongEdges`, `uploadActorPicture(Bulk)`, `createChart`. No more 185-op passthrough.
- **Environment by profile**: `--profile local|prod` (or `SIMULATOR_PROFILE`); local targets a dev pong-server on `:9000` with the same OAuth flow as prod. `.mcp.json` now launches `go run ./cmd/server`.
- **Backend operationIds at the source** + a **spec drift gate**: tool declarations are validated against `internal/tools/testdata/papi-openapi.json` (dumped from the live backend); it already caught a phantom `getApplication` tool. Plus a structural **eval harness** (`eval-scenarios.json`).
- **Tests**: first tests in the module — config, apiclient, tool scenarios (incl. `-race`), drift gate, eval, engine registration.
- **Removed** the legacy tree: `app/mcp-server`, `app/{models,swagger}`, root `main.go`/`specs.go`, `cmd/enrichspec`, and the bundled `sim-public-swagger*.json` (the full-spec embed + enrichspec generator are no longer needed).
- Known limitations: `createActor` takes a numeric `formId` (no `formName` resolution); behavioural (LLM-in-the-loop) eval is a CI/manual step. See `docs/INTEGRATION.md`.
- Clarified that account `amount`/balances are stored as their real decimal value and currency `precision`/`decimals` is display-only (never scale by `10^precision`) — in the `createTransaction`/`finalizeTransaction`/`getAccounts`/`getBalance`/`createCurrency` tool descriptions, the `simulator-finance` skill, and `docs/entities/accounts.md`.
- **Security hardening** of the engine tools: reject non-UUID `layerId`/`actorId`/`filterActorId`/`accountNameId` arguments before any filesystem or API access (closes path traversal in `pullGraphFile`/`pushGraphFile` and URL/query injection); escape graph-file-derived IDs interpolated into API URLs; restrict the `uploadActorPicture(Bulk)` `localPath` source to image extensions with a 25 MiB cap; HTML-escape reflected values in the OAuth callback page; warn when the API base URL would send the auth token over plaintext HTTP to a non-local host. Added `internal/engines/security_test.go`.
- **Linter**: added `golangci-lint` v2 (`make lint`, config `plugins/simulator/mcp-server/.golangci.yml` shared with sibling services). Fixed the real `gosec` findings it surfaced — `ReadHeaderTimeout` on the OAuth callback server (G112), tighter file/dir permissions on written graph files and discovery artifacts (G306/G301); documented the remaining trusted-input taint false positives in `gosec.excludes`. `gosec` is clean; the broader `default: all` set still has a style/modernization backlog (lint is advisory, not yet gating). Documented `make lint` in README / AGENTS / CLAUDE / ARCHITECTURE.
- Synced the lagging `.agents/plugins/marketplace.json` version (was `1.5.0`) with the other manifests.

## [1.5.0]

### Full API coverage
- The MCP server now exposes the **full `/papi/1.0` public API surface (185 operations, up from 48)**. Added a Go generator (`cmd/enrichspec`) that pulls the live OpenAPI doc (`https://mw.simulator.company/api/1.0/doc/json` — served without `operationId`/`summary`), back-fills deterministic camelCase operationIds and summaries, and **reuses the hand-curated specs** so the canonical operationIds the server special-cases depend on (`createActor`, `getForm`, `manageLayer`, `createLink`, `massLink`, …) are preserved. `specs.go` now embeds the generated `swagger/sim-public-swagger.full.json`; the curated `sim-public-swagger-all.json` (80) is kept as the reuse source.
- Added a `Makefile`: `enrich-spec` (regenerate), `check-spec` (drift gate — fails if upstream adds ops missing from the embedded spec), `discovery`, plus `build`/`vet`/`test`.

### Security & reliability fixes
- **OAuth2 CSRF `state`**: the PKCE flow now generates a random `state`, sends it in the authorization URL, and rejects callbacks whose `state` does not match (prevents authorization-code injection).
- **HTTP client hardening**: introduced a single shared `apiHTTPClient()` with a request `Timeout` (the main `executeOperation` client previously had none and could hang a goroutine forever) and connection reuse. TLS certificate verification is now **on by default**; pass `--insecure` only for self-signed gateways (previously verification was hardcoded off in ~10 places).
- **`.env` writes** are now serialised under a mutex and the token + expiry are written in a single pass (no more races / half-written credential files); a token with no derivable `exp` falls back to a conservative 12h window instead of "never expires".
- **`papiGET` (layer pull)** now checks the HTTP status, so a 401/500 no longer silently parses as an empty layer.
- **SVG rasterization** is capped at 4096×4096 (was unbounded → OOM via large `pngWidth/pngHeight`); `looksLikeSVG` precedence bug fixed; fill-check regex hoisted out of the hot loop.
- **base64 image input** now accepts URL-safe and unpadded variants, not only `StdEncoding`.
- **`pushGraphFile`**: an edge whose endpoint isn't placed on the layer is no longer counted as a placed edge (and logs a warning) instead of inflating `EdgesCreated`.
- **`compactGraphLayout`**: cluster height now scales to the tallest cluster, so large clusters no longer overlap the row below.
- Resource-read path check now requires a separator (no sibling-dir prefix bypass); removed dead `setRequestSecurity`; fixed the `set-workspace` description (`WORKSPACE_ID`, not `SIMULATOR_ACC_ID`) and the advertised server name typo (`swagger-mcp`).

### Repo hygiene
- Removed the dead duplicate `./app/` tree (stale copy importing the defunct `git.corezoid.com/mw161089sar/...` paths; canonical code lives under `plugins/simulator/mcp-server/`).
- Rewrote the AI-discovery generator in Go (`cmd/gendiscovery`, replacing `scripts/generate-discovery.py`) so the repo has a single Go toolchain; `public/llms.txt` + `public/.well-known/skills/index.json` are regenerated by `make discovery`.
- Fixed the root `.mcp.json` (it launched a nonexistent `scripts/start-mcp.sh` and passed the obsolete `SIMULATOR_TOKEN`); it now mirrors the working `go run` launcher pointing at `plugins/simulator/mcp-server`.
- Synced the plugin version to `1.5.0` across `.claude-plugin/marketplace.json` and `.agents/plugins/marketplace.json`.

## [1.4.0]

- Add `uploadActorPictureBulk` MCP tool: set pictures on up to 500 actors per call, dedupes identical source images by SHA-256 so the same icon is uploaded once and reused, supports `picture` shortcut to bind an already-uploaded storage path without re-uploading bytes.
- Auto-rasterise SVG sources to PNG inside `uploadActorPicture` (and bulk variant) via pure-Go `oksvg`+`rasterx`: defaults to 256×256, optional `pngWidth`/`pngHeight` overrides, and `svgFillColor` injects a brand colour on the `<svg>` root for monochrome simpleicons. The graph UI doesn't render SVG storage paths, so callers no longer need a local `rsvg-convert` install.

## [1.3.5]

- Add `pruneLongEdges(layerId, maxDistancePx?, bucketThreshold?, preserveParentEdges?, dryRun?)` MCP tool. Walks every edge on a layer, deletes those whose Manhattan distance between endpoints exceeds `maxDistancePx` (default 600 px). By default keeps edges where either endpoint is a hierarchy bucket (≥ `bucketThreshold` incoming edges, default 3). `dryRun:true` previews without deleting. Returns scanned/deleted/kept_short/kept_parent counts plus up to 10 example deletions.

## [1.3.4]

- Add `compactGraphLayout(layerId, strategy)` MCP tool. Implements the `domain-clusters` strategy: actors with `>= bucketThreshold` incoming edges become cluster headers, their children are arranged in a grid under them, and the clusters themselves are laid out in a super-grid (default 4 columns). Orphans stack in a Misc zone. Tunable via `clustersPerRow` / `nodesPerRow` / `nodeDX` / `nodeDY`. One MCP call replaces the full pull → YAML → reposition → push loop. Strategy arg is reserved for future `hierarchical` / `force-directed` layouts.

## [1.3.3]

- Add `getAllLayerPlacements(layerId)` MCP tool: returns every `(actorId, laId, formId, title, position)` row on a layer in one call. The existing `getLayerActorsByFormId` requires the caller to enumerate every formId in use on the layer (often 15+); this tool walks `/graph_layers/paginated/{layerId}?type=nodes` internally instead, paginating to completion.

## [1.3.2]

- Fix `pushGraphFile` not propagating actor positions to the canvas. The internal `updatePositions` helper was sending a bare JSON array to `PUT /graph_layers/actors/{layerId}` and passing `laId` as an integer; the endpoint expects `{"items": [...]}` with `id` as a string and silently no-ops otherwise. Positions in YAML now reach the server on every push.


## [1.3.1]

- Rename edge title fields to camelCase.

## [1.3.0]

- Rebrand to simulator-ai-plugin and ship MCP server.
