BuildPolicy
Editor Integrations
Use VS Code or Zed to edit Chio YAML with diagnostics, completion, hover, and go-to-definition.
What Ships
Chio includes two extensions and a shared snippet pack. The VS Code extension is a TypeScript package; the Zed extension is a Rust crate that compiles to wasm32 for the editor and to a plain rlib on host targets so the workspace build exercises its manifest contract. The snippet source is shared and each editor receives its native format.
| Editor | Package | Identity |
|---|---|---|
| Visual Studio Code | vscode-chio | Publisher backbay, display name Chio, Apache-2.0. |
| Zed | zed-chio | Extension id chio, language server Chio Language Server. |
Supported Documents
Both editors target the same three Chio document types, keyed by filename or suffix. A chio.yaml is a project's config file: it points at a HushSpec policy file or inline block, lists capabilities, and activates guards. Manifests describe tools and capabilities; guard DSL files describe guard pipelines.
| Language ID | Matches | Purpose |
|---|---|---|
chio-yaml | chio.yaml | Project config: policy reference, capabilities, guards, manifest path. |
chio-manifest | *.chio-manifest.yaml | Tool and capability manifest. |
chio-guard | *.chio-guard.yaml | Guard DSL. |
VS Code contributes the three IDs above; Zed contributes a single Chio language scoped to the same suffixes (chio.yaml, chio-manifest.yaml, chio-guard.yaml). Both reuse a YAML tree-sitter grammar for highlighting and scope it to the Chio constructs: capability, policy, and guard keys, and urn:chio:error:* references.
Prerequisites
- A Chio source checkout and a Rust toolchain, to build
chio-lsp. Both extensions spawn that binary; neither bundles it. - For VS Code, Node.js and
npm, to compile the TypeScript client inintegrations/editors/vscode-chio. - For the verification steps, Python 3 to speak LSP over stdio, and
chiofor thedoctorcross-check. - No running kernel, receipt store, or trust-control service. The language server stops at the document boundary.
Install
Both extensions depend on the chio-lsp binary. Build it first and put it on your PATH, or pin an absolute path in the editor settings below.
$ cargo build --release -p chio-lspFinished `release` profile [optimized] target(s) in 0.55s
The binary lands at target/release/chio-lsp. Cargo writes its progress to stderr, and a workspace whose dependency tree is already built prints the one line above; from cold it compiles the graph first.
Load the VS Code extension from its package directory:
$ cd integrations/editors/vscode-chio
$ npm install
$ npm run compile
# Launch via "Run Extension" (F5) inside VS Code, or package it:
$ npx @vscode/vsce packageLoad the Zed extension as a dev extension: open the command palette and run Zed: Install Dev Extension, then point it at integrations/editors/zed-chio.
The language server validates documents
chio-lsp owns the document cache, validation, and completion, hover, and definition catalogs. The client forwards diagnostic codes and completion results without interpreting them.What the language server provides
chio-lsp classifies each open document by language ID or file suffix and serves four request classes from a shared document cache. It stops at the document boundary: it does not run the kernel, evaluate policy, or read arbitrary project files.
| Feature | Documents | Behavior |
|---|---|---|
| Diagnostics | all three | Structural required-key and shape checks over parsed YAML on didOpen / didChange, one provider per language. |
| Completion | chio.yaml only | Capability scopes under scopes / tools / capabilities, guard identifiers under guards, and top-level policy keys. |
| Hover | all three | Registry help on urn:chio:scope:*, urn:chio:guard:*, and urn:chio:error:* identifiers. |
| Go to definition | all three | Resolves a urn:chio:scope:* / urn:chio:guard:* reference to a linked manifest first, then the first occurrence in the open document. |
The chio.yaml top-level keys the completion catalog offers are version, policy, capabilities, guards, and manifest. Of these, version and policy are required: a chio.yaml missing either surfaces a diagnostic carrying urn:chio:error:cli:doctor-chio-yaml-invalid, the same code the chio doctor probe emits, so the editor and the CLI agree on what counts as valid.
Verify the result
An editor that silently fails to start its language server looks exactly like a project with no problems. Speak LSP to the binary directly and you get an answer either way. This script sends framed JSON-RPC over stdio, the same transport the extensions use, and prints one reply. It runs chio-lsp from CHIO_LSP when that is set and from PATH otherwise, which is the resolution order both extensions use.
import json, os, subprocess, sys, time
def frame(msg):
body = json.dumps(msg).encode()
return b"Content-Length: %d\r\n\r\n%s" % (len(body), body)
def replies(messages):
server = os.environ.get("CHIO_LSP", "chio-lsp")
proc = subprocess.Popen([server], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
for msg in messages:
proc.stdin.write(frame(msg))
proc.stdin.flush()
time.sleep(0.5)
proc.stdin.close()
raw, out = proc.stdout.read().decode(), []
while "Content-Length: " in raw:
head = raw.index("Content-Length: ")
gap = raw.index("\r\n\r\n", head)
size = int(raw[head + 16 : gap])
out.append(json.loads(raw[gap + 4 : gap + 4 + size]))
raw = raw[gap + 4 + size :]
return out
OPEN = {"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"processId": None, "rootUri": None, "capabilities": {}}}
READY = {"jsonrpc": "2.0", "method": "initialized", "params": {}}
BAD_CONFIG = {"jsonrpc": "2.0", "method": "textDocument/didOpen", "params": {"textDocument": {
"uri": "file:///work/chio.yaml", "languageId": "chio-yaml", "version": 1,
"text": "version: 1\ncapabilities: []\n"}}}
if sys.argv[1] == "handshake":
for reply in replies([OPEN]):
if "result" in reply:
print(json.dumps(reply["result"], indent=2))
else:
for reply in replies([OPEN, READY, BAD_CONFIG]):
if reply.get("method") == "textDocument/publishDiagnostics":
print(json.dumps(reply["params"]["diagnostics"], indent=2))The handshake is the first check. A working server answers initialize with the four capabilities the feature table describes and identifies itself by name and version:
$ python3 lsp-probe.py handshake{
"capabilities": {
"completionProvider": {
"triggerCharacters": [
":",
" ",
"-"
]
},
"definitionProvider": true,
"hoverProvider": true,
"textDocumentSync": 1
},
"serverInfo": {
"name": "chio-lsp",
"version": "0.1.0"
}
}textDocumentSync: 1 is the LSP full-sync kind: every edit resends the whole document, which is why the server can validate from the cache without reading the file from disk. The completion trigger characters are ":", " ", and "-", so completions fire after a key and inside a list item. Nothing in the reply advertises a pull-diagnostics provider: diagnostics are pushed through textDocument/publishDiagnostics when a document opens or changes.
The second check is the refusal. Open a chio.yaml that has version but no policy and the server publishes a diagnostic for it unprompted:
$ python3 lsp-probe.py diagnostic[
{
"code": "urn:chio:error:cli:doctor-chio-yaml-invalid",
"message": "chio.yaml is missing required key `policy`.",
"range": {
"end": {
"character": 0,
"line": 0
},
"start": {
"character": 0,
"line": 0
}
},
"severity": 1,
"source": "chio-lsp"
}
]That is the whole contract an editor renders: code is the registry URN, source is chio-lsp, and severity: 1 is the LSP error level. A missing required key has no token to point at, so its range collapses to the start of the document; a parse error carries the line and column the YAML parser reported.
Configuration
By default both editors resolve chio-lsp on PATH and spawn it with no extra arguments. Override the binary path or forward flags when the server lives somewhere non-standard.
VS Code
The extension contributes three settings under the chio namespace.
| Setting | Default | Description |
|---|---|---|
chio.lsp.path | chio-lsp | Path to the server binary. Resolved on PATH when left as the default. |
chio.lsp.args | [] | Extra arguments forwarded to the server verbatim on spawn. |
chio.trace.server | off | LSP trace verbosity: off, messages, or verbose. |
{
"chio.lsp.path": "/opt/chio/bin/chio-lsp",
"chio.lsp.args": [],
"chio.trace.server": "off"
}Zed
Zed reads the standard lsp.<server>.binary block. The server id is chio-lsp. An empty or whitespace-only path falls back to the bare chio-lsp invocation, so the default PATH lookup still works.
{
"lsp": {
"chio-lsp": {
"binary": {
"path": "/opt/chio/bin/chio-lsp",
"arguments": ["--verbose"]
}
}
}
}Both editors let you override the binary path and launch arguments.
Snippets
The extensions include four snippets for common authoring patterns. Type a prefix and expand it; each snippet uses LSP-standard ${1:label} placeholders so both editors accept the same source.
| Prefix | Scaffold | Scope |
|---|---|---|
capability-allowlist | Capability allowlist with explicit scope bounds. | chio-yaml, chio-manifest |
scope-bound | Scope-bound capability fragment with TTL and tenant restriction. | chio-yaml, chio-manifest |
guard-pipeline | Guard pipeline composing input, decision, and output stages. | chio-yaml, chio-guard |
manifest-skeleton | Minimal manifest with policy, capabilities, and guard references. | chio-manifest |
Expanding manifest-skeleton drops in a complete starting manifest with the default values already filled:
version: 1
name: manifest_name
policy:
default: deny
capabilities:
- id: capability_id
scope:
- scope.action
guards:
- ref: guard.identifierSnippet sources are tool-neutral YAML at integrations/editors/snippets/*.snippet.yaml, validated against a JSON schema. A build task renders them into each editor's native format; the generated files carry a generated by cargo xtask snippets regen header and are checked for drift in CI.
$ cargo xtask snippets regen # regenerate native files
$ cargo xtask snippets regen --check # CI drift check (no writes)The check form prints one line and exits zero when the generated files match their sources. Anything else is drift. This is the same invocation the .github/workflows/spec-drift.yml job runs, so a local pass and a CI pass are the same check:
$ cargo xtask snippets regen --checksnippets in sync (4 snippet sources)
Diagnostics and registry codes
Every diagnostic carries a registry code in its LSP code field, formatted as urn:chio:error:<domain>:<code>, for example urn:chio:error:capability:scope-mismatch. The editors render the URN unchanged; downstream tooling matches on the code field programmatically. The source is chio-lsp. Every range is zero-width, so an editor renders a caret rather than a span: a parse error sits at the line and column the YAML parser reported, and a structural complaint with no token to blame sits at the start of the document.
One vocabulary of errors
Failures and recovery
| Symptom | Cause and recovery |
|---|---|
| No diagnostics, no completions, no hover, in any file. | The client could not spawn the server. Run the handshake probe: if it fails too, the binary is missing from PATH. Pin an absolute path in chio.lsp.path or the Zed binary.path. |
| The server starts, but one file gets no diagnostics. | That document classified as Other. Classification takes the reported language id first and the path suffix second, so a policy file named something other than chio.yaml, *.chio-manifest.yaml, or *.chio-guard.yaml is cached and left alone. Rename it or set the language id by hand. |
Completions appear in chio.yaml but nowhere else. | Working as built. Completion serves chio.yaml only; manifests and guard documents get diagnostics, hover, and go-to-definition. |
| A capability or guard completion list looks short. | The catalogs are curated, not derived from your project. They offer a starting vocabulary, not an inventory of what you have declared. |
cargo xtask snippets regen --check fails. | A generated editor snippet file drifted from its source. Run the command without --check and commit what it writes; the sources are the four *.snippet.yaml files, and the editor files are outputs. |
When the editor and the CLI disagree about a chio.yaml, they should not: both check the same required keys and emit the same registry code. chio doctor on the same file the diagnostic capture used:
$ chio doctor --skip-network | grep -E 'chio_yaml|missing:' [error] chio_yaml (urn:chio:error:cli:doctor-chio-yaml-invalid): ~/chio/chio.yaml is missing required keys: policy
missing: policyOther editors
Any LSP-capable editor (Neovim, Helix, JetBrains, Emacs lsp-mode) can use chio-lsp without a first-party extension. Configure:
- Binary:
chio-lsp, resolved onPATHby default, pinnable to an absolute path. - Transport: LSP over stdio, no environment variables required.
- Selectors: map the client to
chio.yaml,*.chio-manifest.yaml, and*.chio-guard.yaml. - Diagnostics: read the
urn:chio:error:*code from each diagnostic'scodefield and render it as-is.
Next steps
- Write a Policy · author HushSpec rules that the guard pipeline enforces
- HushSpec Policy Format · the policy schema used by editor completions
- chio.yaml Configuration · the project config file the extensions validate
- Capabilities · scopes and bounds referenced by the capability snippets