# OpenSpec for Infra — Hands-on Validation (2026-09) Full end-to-end test on this host. All commands verified. ## Setup ```bash # node 22 present at ~/.local/bin npm install -g @fission-ai/openspec@latest # v1.12.0, binary -> /opt/hermes/.hermes/node/bin/openspec mkdir -p /opt/hermes/openspec-lab && cd /opt/hermes/openspec-lab openspec init --tools hermes --force --no-animation # Output: "6 skills in .hermes/", "Commands skipped for: hermes (no adapter)" # Structure created: # .hermes/skills/openspec-{propose,explore,apply-change,update-change,sync-specs,archive-change}/SKILL.md # openspec/config.yaml # openspec/specs/.gitkeep, openspec/changes/archive/.gitkeep ``` Hermes wiring: ```bash hermes config set skills.external_dirs '["/opt/hermes/openspec-lab/.hermes/skills"]' hermes config get skills # -> external_dirs: '["..."]' ``` ## Infra config.yaml (working) ```yaml schema: spec-driven context: | Домен: self-hosted инфраструктура (homelab / VPS), Linux (Ubuntu/Debian), Docker (docker compose), systemd. Окружение: Hermes-агент на хосте, SSH-доступ к VPS. Ключевые сервисы: prosody (XMPP + Slidge мост), netbox (docker), garage (S3), searxng, mtproto-proxy, nntp, tproxy-web-proxy, telegram-tunnel. Управление: systemd-юниты, docker-compose.yml, конфиги в /opt//; рестарт сервисов — только извне (SSH sudo systemctl restart). Соглашения: отдельный каталог /opt// на сервис; секреты в .env; порты фиксируются в README.md; апдейты через git. rules: proposal: - Указывать затронутые сервисы и порты - Включать план отката (rollback) specs: - Требования в формате MUST/SHOULD/MAY - Сценарии проверки: GIVEN/WHEN/THEN с конкретными командами верификации (systemctl status, curl, docker ps) design: - Указывать конкретные файлы конфигов и юнитов - Включать команды применения и проверки tasks: - Каждая задача — проверяемый шаг с командой верификации ``` ## Change lifecycle test (add-vpn-tunnel-proxy) ```bash openspec new change add-vpn-tunnel-proxy # -> openspec/changes/add-vpn-tunnel-proxy/.openspec.yaml (schema: spec-driven, created: 2026-09-06) openspec status --change add-vpn-tunnel-proxy --json # artifacts: proposal, specs (outputPath "specs/**/*.md"), design, tasks # applyRequires: ["tasks"], planningHome.root, actionContext.allowedEditRoots openspec instructions proposal --change add-vpn-tunnel-proxy --json # -> instruction (Why/What Changes/Capabilities/Impact), template, context (injected), rules # NOTE: stderr warns "Rules for 'specs' must be an array of strings, ignoring this artifact's rules" — harmless ``` Wrote the 4 artifacts by hand (proposal.md, specs/tunnel-proxy/spec.md with ADDED requirements + GIVEN/WHEN/THEN scenarios, design.md with files/commands/rollback, tasks.md with checkboxes). ```bash openspec validate add-vpn-tunnel-proxy # -> "Change 'add-vpn-tunnel-proxy' is valid" openspec status --change add-vpn-tunnel-proxy # -> 4/4 artifacts complete openspec list # -> add-vpn-tunnel-proxy 0/7 tasks # Without --yes, archive fails (asks y/N, no stdin): openspec archive add-vpn-tunnel-proxy # -> error: 7 incomplete tasks, no answer from stdin sed -i 's/^- \[ \]/- [x]/' openspec/changes/add-vpn-tunnel-proxy/tasks.md openspec archive add-vpn-tunnel-proxy --yes # -> "Specs updated successfully. Change archived as '2026-09-06-add-vpn-tunnel-proxy'." # -> openspec/specs/tunnel-proxy/spec.md created (2 requirements merged) # -> openspec/changes/archive/2026-09-06-add-vpn-tunnel-proxy/ (all 4 artifacts) ``` Merged main spec (auto-generated): ```markdown # tunnel-proxy Specification ## Purpose TBD - created by archiving change add-vpn-tunnel-proxy. Update Purpose after archive. ## Requirements ### Requirement: Persistent SOCKS5 Tunnel The system MUST maintain a persistent SOCKS5 proxy tunnel from the host to VPS01, listening on 127.0.0.1:1080. #### Scenario: Tunnel starts on boot - GIVEN the host boots ... #### Scenario: Tunnel dies ... ``` ## Gotchas hit 1. `openspec init --no-init-git` → "unknown option" — that flag does not exist in v1.12.0 (git init runs by default). 2. `openspec instructions ... --json | python3 -m json.tool` → JSON decode error because warnings go to **stdout**, not stderr. Use `> out.json 2> err.txt`. 3. Rules for `specs` artifact always warn (even well-formed) — cosmetic, ignore. 4. Archive needs tasks done → use `--yes` after completing tasks. 5. `hermes config get skills` prints external_dirs as a string — normal.