# Validation Extension Protocol (one page) The framework core is **engine-agnostic**: it runs *external validators* at its existing validation timings. Adding a new validation method = **adding an entry** to the config; **no core code change** (open-closed). The point lives in `argo/scripts/validation-extensions.js`; the ontology engine in this directory is one such entry. ## Where it is configured `ARGO_VALIDATION_EXTENSIONS` — a `.env` key (in `~/.argo/.env`), a JSON **array**: ```json [ { "name": "ontology", "timings": ["write", "init"], "command": ["node", "/scripts/ontology-projection/cli.js", "--workspace", "{workspaceRoot}", "--graph", "{graphPath}", "--mode", "{mode}"] } ] ``` - `name` — a label shown in the merged findings. - `timings` — where to run: `"write"` (per write mutation) and/or `"init"` (workspace init / full validation). One entry may list both. - `command` — argv array, run with `shell:false`. Empty/absent config ⇒ no-op (default behaviour unchanged). ## Tokens (argv) | Token | Substituted with | |---|---| | `{workspaceRoot}` | the resolved workspace root | | `{graphPath}` | workspace-relative graph path (`design/KG/SystemArchitecture.json`) | | `{mode}` | `write` or `init` | ## Context passed via environment | Env var | Meaning | |---|---| | `ARGO_VALIDATION_GRAPH_FILE` | path to the **post-mutation** document (write mode); empty in `init` mode — read the file, not the on-disk graph | | `ARGO_VALIDATION_TOUCHED_ELEMENTS` | JSON array of element ids touched by this write | | `ARGO_VALIDATION_TOUCHED_RELATIONSHIPS` | JSON array of relationship ids touched by this write | | *(inherited)* | the validator also inherits the process env (incl. the rest of `~/.argo/.env`) | The validator MUST be **read-only** w.r.t. canonical: findings are a derived read-model; never write the graph back. ## Output protocol Print **one JSON object** to stdout (the **last** parseable JSON line wins): ```json { "errors": ["", "..."], "warnings": ["", "..."] } ``` - `errors` → merged into the framework's validation errors (**blocking** at that timing). - `warnings` → surfaced but never block. - **Fail-close (default)**: if the command is missing / exits non-zero, the framework records a **blocking error** — a validator you configured is never silently skipped. Set `"onError":"warn"` on an entry to make an advisory validator's infrastructure failures non-blocking (warn). ## Timings - **`write`**: runs on every preview/apply mutation, on the **post-mutation document**. Scope yourself to the delta using the touched ids (bounded cost). - **`init`**: runs on workspace init / `validateSystemArchitecture`, on the whole graph (offline policy). ## Adding a new validator (open-closed) 1. Write a command that: reads the post-mutation doc (env), optionally the touched ids, computes findings, prints `{errors,warnings}`. 2. Add **one object** to `ARGO_VALIDATION_EXTENSIONS`. 3. Restart the host so the argo MCP reloads `~/.argo/.env`. **Boundary (this needs a core change, not config):** a *new timing*, *new context data*, or a *different output shape* widens the contract — that is a deliberate, reviewed change to `validation-extensions.js` (and its call sites), not an entry. ## Minimal example (Node) ```js // my-validator.js — argv: --workspace --graph --mode const fs = require('node:fs'); const doc = JSON.parse(fs.readFileSync(process.env.ARGO_VALIDATION_GRAPH_FILE || '', 'utf8')); const errors = (doc.elements || []).filter(e => !e.name).map(e => `element ${e.id} has no name`); process.stdout.write(JSON.stringify({ errors, warnings: [] })); ``` Entry: `{"name":"names","timings":["write","init"],"command":["node","/path/my-validator.js","--mode","{mode}"]}` ## Verify ``` Get-Content "$env:USERPROFILE\.argo\.env" | Select-String ARGO_VALIDATION_EXTENSIONS ``` Then restart the host; the merged findings appear in write results (`errors` / `validationWarnings`) and in `validateSystemArchitecture` output.