From ae6bd08ad83e1b25e33352e1690d7bf83f4b4150 Mon Sep 17 00:00:00 2001 From: kami Date: Thu, 30 Jul 2026 23:40:45 +0400 Subject: [PATCH] Add a Makefile, a README and the review this branch works from MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was no onboarding doc of any kind. The Makefile covers build, test, vet, fmt, a fmt-check gate, tidy, run, the MCP stdio mode, docker-build and the ecosystem compose targets. The README covers what Hexis is and where it sits between Nexus, Praxis and workspace-mcp, how to build and run it, the flag and environment table including the new HEXIS_API_TOKEN, the API surface, and the tests. REVIEW-2026-07-30.md is the engineering review the preceding commits address, kept in-tree as the rationale for them. It includes an independent second-reviewer pass, and its "uncertainties" section has since been resolved: the service is not reachable from the public internet (the internet-exposed nginx config in the Maven repo is a template, not what is deployed), Nexus has no blessing concept and none is planned, and the running image was built from an uncommitted working tree hours before the first commit existed — which is why the deployed binary never matched any revision. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Uea55zaiWuEByEDC4UBSdd --- Makefile | 73 ++++++++++++++++ README.md | 204 +++++++++++++++++++++++++++++++++++++++++++ REVIEW-2026-07-30.md | 177 +++++++++++++++++++++++++++++++++++++ 3 files changed, 454 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 REVIEW-2026-07-30.md diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ab14d9e --- /dev/null +++ b/Makefile @@ -0,0 +1,73 @@ + +# Hexis — capability registry + guarded execution. +# Pure-Go build (modernc sqlite), so CGO is never required. + +GO ?= go +BIN_DIR ?= bin +IMAGE ?= hexis:dev +DATA_DIR ?= $(HOME)/.local/share/hexis +HTTP_ADDR ?= localhost:9741 + +.DEFAULT_GOAL := build + +.PHONY: help +help: ## List targets + @grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | \ + awk -F':.*?## ' '{printf " %-14s %s\n", $$1, $$2}' + +.PHONY: build +build: ## Build hexisd and hexisctl into bin/ + CGO_ENABLED=0 $(GO) build -trimpath -o $(BIN_DIR)/hexisd ./cmd/hexisd + CGO_ENABLED=0 $(GO) build -trimpath -o $(BIN_DIR)/hexisctl ./cmd/hexisctl + +.PHONY: test +test: ## Run the test suite + $(GO) test ./... + +.PHONY: vet +vet: ## Run go vet + $(GO) vet ./... + +.PHONY: fmt +fmt: ## Format all Go sources + $(GO) fmt ./... + +.PHONY: fmt-check +fmt-check: ## Fail if any Go source is unformatted + @out="$$(gofmt -l .)"; \ + if [ -n "$$out" ]; then echo "unformatted files:"; echo "$$out"; exit 1; fi + +.PHONY: check +check: fmt-check vet test ## fmt-check + vet + test + +.PHONY: tidy +tidy: ## Tidy go.mod/go.sum + $(GO) mod tidy + +.PHONY: run +run: build ## Run hexisd locally (HTTP API) + $(BIN_DIR)/hexisd -http $(HTTP_ADDR) -data $(DATA_DIR) + +.PHONY: run-mcp +run-mcp: build ## Run hexisd as an MCP stdio server + $(BIN_DIR)/hexisd -mcp -data $(DATA_DIR) + +.PHONY: clean +clean: ## Remove build output + rm -rf $(BIN_DIR) + +.PHONY: docker-build +docker-build: ## Build the distroless container image + docker build -t $(IMAGE) . + +# The compose file that actually runs Hexis lives with the ecosystem stack, not +# in this repo. Override COMPOSE_DIR if yours is elsewhere. +COMPOSE_DIR ?= ../Maven/deploy/ecosystem + +.PHONY: compose-up +compose-up: ## Build + start the ecosystem stack (nexus, praxis, hexis) + docker compose -f $(COMPOSE_DIR)/docker-compose.yml up -d --build hexis + +.PHONY: compose-logs +compose-logs: ## Tail hexis logs from the ecosystem stack + docker compose -f $(COMPOSE_DIR)/docker-compose.yml logs -f hexis diff --git a/README.md b/README.md new file mode 100644 index 0000000..d680f10 --- /dev/null +++ b/README.md @@ -0,0 +1,204 @@ +# Hexis + +Capability registry and **guarded execution** service for the Nexus / Praxis / Hexis +ecosystem (see `ECOSYSTEM-SPEC.md` §4 in the sibling repo root). + +Hexis is the only component allowed to *act* on infrastructure. It holds a registry +of named capabilities (`workspace.docker.restart`, …), each bound to a registered +provider implementation — never to a shell string — and mediates every execution +behind a set of guards: + +- a capability must be `enabled`; +- capabilities marked `requires_confirmation` need a valid, unexpired confirmation + matching the capability, target and argument hash (default TTL applies); +- one in-flight execution per `(capability, target_entity_id)` — a second attempt + gets `409`; +- a wall-clock timeout per capability; on timeout the outcome is `unknown` + (never `failed`, never auto-retried). + +Its place in the ecosystem: + +- **Nexus** (`:9740`) owns entities. Hexis resolves free-text targets to entity IDs + through Nexus rather than inventing them. +- **Praxis** (`:8989`) owns items/incidents and consumes execution correlation. +- **Hexis** (`:9741`) owns capabilities, confirmations and executions. +- Consumers today: Maven (voice + mavweb) over the HTTP API, plus MCP clients via + the stdio adapter. + +Actual work is performed by providers. The live one is **workspace_mcp**, which +proxies an allowlisted set of tools from a Workspace MCP HTTP server (docker, +filesystem, …). There is also a `systemd` provider stub, which no capability +currently registers. + +> Status note: this repo has a candid engineering review in +> `REVIEW-2026-07-30.md`. Read it before trusting the guards — several are +> documented there as incomplete at the time of writing. + +## Build + +Go 1.25+. No CGO (sqlite is the pure-Go `modernc.org/sqlite`). + +```sh +make build # -> bin/hexisd, bin/hexisctl +make check # gofmt check + go vet + go test +``` + +Individual targets: `make test`, `make vet`, `make fmt`, `make fmt-check`, +`make tidy`, `make clean`, `make help`. + +## Run locally + +```sh +export HEXIS_API_TOKEN=dev-token # required; hexisd refuses to start without it +make run # bin/hexisd -http localhost:9741 -data ~/.local/share/hexis +``` + +That runs registry + execution API with no workspace provider, so nothing is +executable — useful for API work. To wire the real provider: + +```sh +bin/hexisd \ + -http localhost:9741 \ + -data ~/.local/share/hexis \ + -workspace-url http://localhost:9930 \ + -workspace-allowlist etc/workspace-allowlist.yaml +``` + +On startup Hexis discovers the workspace tools and registers a capability for each +allowlisted entry. Discovery failure is a warning, not fatal; a missing or +unreadable allowlist file **is** fatal when `-workspace-url` is set. + +MCP stdio mode (same storage, same engine, tools `hexis.list_capabilities`, +`hexis.inspect_capability`, `hexis.resolve_target`, `hexis.execute`, +`hexis.execution_status`): + +```sh +make run-mcp # bin/hexisd -mcp -data ~/.local/share/hexis +``` + +`bin/hexisctl` is a thin CLI over the HTTP API: + +```sh +bin/hexisctl health +bin/hexisctl capability list [--entity ENTITY_ID] +bin/hexisctl capability show +bin/hexisctl exec [--args JSON] [--idempotency KEY] +bin/hexisctl execution +``` + +## Configuration + +Flags (all optional) on `hexisd`: + +| Flag | Default | Meaning | +| --- | --- | --- | +| `-http` | `localhost:9741` | HTTP listen address | +| `-data` | `$HOME/.local/share/hexis` | Data dir; SQLite lives at `/hexis.db` | +| `-mcp` | `false` | Run the MCP stdio adapter instead of the HTTP server | +| `-workspace-url` | `$WORKSPACE_MCP_URL` | Workspace MCP HTTP API URL, e.g. `http://localhost:9930`. Empty ⇒ provider disabled | +| `-workspace-allowlist` | `/workspace-allowlist.yaml` | Tool allowlist YAML (repo copy: `etc/workspace-allowlist.yaml`) | +| `-nexus` | `$HEXIS_NEXUS_URL`, else `http://localhost:9740` | Nexus base URL, used by `hexis.resolve_target` | + +Environment: + +- `WORKSPACE_MCP_URL` — fallback for `-workspace-url`. +- `HEXIS_NEXUS_URL` — fallback for `-nexus`. +- `HEXIS_URL` — read by `hexisctl` only; defaults to `http://localhost:9741`. +- `HEXIS_API_TOKEN` — shared bearer token; required by `hexisd`, and also read by + `hexisctl` (and by `pkg/client` via `WithToken`) to authenticate its calls. + +**Authentication.** The whole `/api/v1/` surface sits behind a shared bearer +token read from `HEXIS_API_TOKEN`; `/health` and `/ready` stay open for probes. +It fails closed: `hexisd` refuses to start without the variable set, and the +middleware answers `503` rather than serving unauthenticated if it is somehow +empty. Callers send `Authorization: Bearer `; a missing or wrong token +gets `401`. + +```sh +export HEXIS_API_TOKEN="$(openssl rand -hex 32)" +curl -H "Authorization: Bearer $HEXIS_API_TOKEN" localhost:9741/api/v1/capabilities +``` + +(This landed while the README was being written — if the details drift, `internal/api/handler.go` +`requireAuth` is the source of truth.) + +Ports: Hexis `9741`, Nexus `9740`, Praxis `8989`, Workspace MCP `9930`. + +## API surface + +All `/api/v1/` responses are JSON. A request may send `X-Hexis-Version`; if +present it must be `v1`, otherwise the request is rejected with `412`. Omitting +the header is allowed. + +| Method | Path | Notes | +| --- | --- | --- | +| `GET` | `/health` | liveness | +| `GET` | `/ready` | readiness | +| `GET` | `/api/v1/capabilities` | optional `?entity_id=` filter | +| `POST` | `/api/v1/capabilities` | register a capability | +| `GET` | `/api/v1/capabilities/{id}` | | +| `DELETE` | `/api/v1/capabilities/{id}` | | +| `POST` | `/api/v1/confirmations` | `{capability_id, target_entity_id, arguments?, requester?}` → `201` with the confirmation | +| `POST` | `/api/v1/execute` | `{capability_id, target_entity_id, arguments?, confirmation_id?, …}`; also reads `X-Correlation-ID` / `X-Causation-ID` | +| `GET` | `/api/v1/executions` | history; `?entity_id=`, `?since=`, `?limit=` (≤100) | +| `GET` | `/api/v1/executions/{id}` | | +| `GET` | `/api/v1/changes` | event feed, `?since=`, up to 100 events | + +Execute status codes: `409` in-flight duplicate, `404` unknown capability, `403` +confirmation/enablement failures, `400` otherwise. + +### Pagination + +`/api/v1/executions` and `/api/v1/changes` share one cursor convention: `since` +is an **exclusive integer sequence**, results come back in ascending order, and +you page by passing the sequence of the last item you saw. Executions carry it +as `seq`, events as `sequence`. A full page means "call again"; there is no +separate next-page token, and there is no timestamp cursor. + +### Capability wire shape + +Every producer — the HTTP handler, the MCP adapter and `pkg/client` — serializes +a capability through the single shape defined in `pkg/client/capability.go`, +converted by `internal/wire`. Notable points: + +- `capability_id` (the spec §4.1 name) and `id` are **both** emitted and always + carry the same value. `id` is a deprecated alias retained for Maven's client; + see the compatibility note in `pkg/client/capability.go` before removing it. +- `enabled` and `requires_confirmation` are always present, including when + false. They are derived server-side from the risk tier and are never settable + by a caller. + +## Tests + +```sh +make test # go test ./... +go test ./internal/execution/ -run TestConfirmation -v +``` + +Tests are table-driven Go tests colocated with their packages — currently +`internal/api/handler_test.go`, `internal/execution/{engine,contract}_test.go`, +`internal/provider/workspace_mcp_test.go`. They use temporary SQLite files and +need no running services. + +## Container and deploy + +```sh +make docker-build # distroless image, static CGO_ENABLED=0 binary +``` + +The image entrypoint is `/hexisd -data /data -http 0.0.0.0:9741`, so the data +volume must be mounted at `/data`, `HEXIS_API_TOKEN` must be in the container +environment, and — when `WORKSPACE_MCP_URL` is set — the allowlist must be present +at `/data/workspace-allowlist.yaml`, or the container exits immediately. + +Hexis is deployed as part of the ecosystem compose stack, which lives outside this +repo (`Maven/deploy/ecosystem/docker-compose.yml`, alongside nexus and praxis; +host nginx fronts it). Convenience wrappers: + +```sh +make compose-up # build + start just the hexis service +make compose-logs +``` + +Override `COMPOSE_DIR` if your checkout layout differs from +`../Maven/deploy/ecosystem`. diff --git a/REVIEW-2026-07-30.md b/REVIEW-2026-07-30.md new file mode 100644 index 0000000..7ee2e9f --- /dev/null +++ b/REVIEW-2026-07-30.md @@ -0,0 +1,177 @@ +# Hexis — Senior Engineering Review + +Date: 2026-07-30 +Reviewed at: commit `945e4ba` (working tree had one uncommitted change in `cmd/hexisd/main.go`: default Nexus URL `8987` → `9740`) +Reference contract: `/home/kami/apps/ECOSYSTEM-SPEC.md` §4 + +--- + +## verdict + +**Fragile + misaligned, but structurally sound and worth repairing incrementally.** No rewrite. The architecture matches the spec's shape; the *guards the spec exists to enforce* are missing, and the primary execution path is functionally dead at HEAD. + +--- + +## first assessment + +```text +purpose: capability registry + guarded execution for the Nexus/Praxis/Hexis + ecosystem (ECOSYSTEM-SPEC.md §4) +intended users: Maven (voice + mavweb), Command Center, MCP clients +actual users: Maven only — mavweb reads /api/v1/capabilities; mavend/voice.go:1329-1381 + resolves→matches→executes via a *vendored* copy of pkg/client +critical workflows: 1. list capabilities for an entity 2. confirm 3. execute 4. poll changes +current state: running on :9741 — but the live binary predates commit 89d8433 + (its GET capability response has no `enabled`/`requires_confirmation` + fields), so deployed ≠ HEAD +known failures: every workspace_mcp capability is registered with enabled=false (verified) +maintenance burden: low — 3.6k LOC, 1 real dependency + pure-Go sqlite, builds clean, vet clean +technical constraints: single-user homesrv, single SQLite writer, distroless container +personal constraints: solo maintainer; spec is the contract, three services must agree +what still works well: confirmation lifecycle (args_hash + version + TTL) is correct and + genuinely well tested; storage layer is honest; timeout⇒unknown is right +what has become obsolete: systemd provider (no systemctl in distroless), ListenUnix, the + contract_test.go "bug documentation" tests, schema_migrations table +``` + +Classification: **fragile** (safety guards absent), **misaligned** (deployed ≠ HEAD; spec §4.3/§4.4 unmet), partly **underbuilt** (no auth, no arg validation). + +--- + +## main findings + +### 1. Every workspace capability is registered disabled — the product's only working provider cannot execute + +- **problem:** `BuildCapabilities()` never sets `Enabled`, so it defaults to `false`. +- **evidence:** `internal/provider/workspace_mcp.go:235-249` — the `domain.Capability` literal omits both `Enabled` and `TimeoutSeconds`, leaving them zero-valued. Note that the same function *skips* allowlist-disabled tools at `:226`, so it plainly intends everything it emits to be live: this is an omitted field, not a policy decision. A throwaway test at HEAD printed `Enabled=false RequiresConfirmation=false TimeoutSeconds=0`. `internal/execution/engine.go:41` then returns `ErrCapabilityDisabled` → 403 for all 19 capabilities. +- **impact:** Maven's voice execute path can never succeed against a fresh database. Nothing in the suite catches it — no test executes a workspace-provided capability end to end. +- **action:** **repair.** Set `Enabled` from risk tier and `TimeoutSeconds` explicitly. Must not land alone — see finding 2. + +### 2. Fixing (1) naively arms unconfirmed docker stop/restart on a publicly-proxied, unauthenticated port + +- **problem:** `BuildCapabilities` also leaves `RequiresConfirmation=false` for `risk: medium` docker start/stop/restart, and the HTTP API has no authentication whatsoever. +- **evidence:** `Maven/deploy/ecosystem/nginx.conf:32-42` proxies `hexis.kvmx.ru:80` → `127.0.0.1:9741` with no auth block; `internal/api/handler.go:37` exposes `POST /api/v1/execute`; `handler.go:115-166` lets any caller `POST /api/v1/capabilities` with `provider`+`operation` of their choosing. Precisely: `handler.go:142` derives the correct spec §4.3 default (`enabled := req.Risk != "destructive"`), and then `:143-145` lets an optional `enabled` field in the request body override it. The guard is implemented and then made opt-out by the untrusted caller — worse than simply absent, because it reads as enforced. `requires_confirmation` (`:159`) is copied from the body with no derivation at all. +- **concrete attack path:** `POST http://hexis.kvmx.ru/api/v1/capabilities {"name":"x","provider":"workspace_mcp","operation":"ssh.exec","enabled":true}` then `POST /api/v1/execute`. The allowlist blocks that specific one (`workspace_mcp.go:129-135` re-checks `ssh.exec` is disabled) — but `docker.stop_container` is allowed, and self-registration bypasses `requires_confirmation` entirely. +- **impact:** remote unauthenticated container control. +- **action:** **security, blocking.** Require a shared token on `/api/v1/`; derive `requires_confirmation` from risk tier server-side; and stop honouring `enabled` from the request body entirely rather than treating it as an override. + +### 3. Hexis accepts free-text targets — the one thing the spec says it must never do + +- **problem:** `target_entity_id` is accepted as any non-empty string. Nothing checks the `ent_` shape, existence in Nexus, or `capability.TargetTypes`. +- **evidence:** `handler.go:213-216` checks non-empty only; `engine.go:44-46` only compares against a *pinned* `TargetEntityID`, which is `""` for all 19 registered capabilities. Spec §4.3: "Hexis never accepts a free-text target. Ever." +- **impact:** the invariant "Maven must not invent mappings" is unenforced at the point it matters. +- **action:** **repair.** Validate the target against Nexus `GET /api/v1/entities/{id}` (that endpoint exists) plus a `TargetTypes` match, before creating the execution. +- **note:** the spec's stronger *blessing* guard (§4.3) is **not implementable today** — `grep -rni bless` across `nexus/` returns nothing. Nexus has no blessing concept. That's a cross-service gap, not a Hexis bug. + +### 4. Audit fields are written but never read back, or never written at all + +- **problem:** `confirmation_id` is INSERTed but absent from every SELECT, so it always reads back empty. `causation_id` isn't in the `executions` schema at all, yet `engine.go:79` sets it on the struct — the field is populated in memory with nowhere to land. +- **evidence:** `confirmation_id` appears in the INSERT at `sqlite.go:295` and in **none** of the three executions SELECTs (`:313`, `:369`, `:418`). `causation_id` exists only on `hexis_events` (`:119`, `:502`), never on `executions`. Also missing vs spec §4.1: `capability_version` on executions (it exists on `confirmations` at `:134`, so the precedent is there). +- **impact:** "which confirmation authorized this destructive act" is unanswerable from the API — the core audit question. Silent, not a crash. +- **action:** **repair.** Add the columns as a migration, persist and select them. + +### 5. `contract_test.go` asserts the opposite of current behavior and passes anyway + +- **problem:** three tests are prose, not verification. `TestContract_ChangesSinceAlwaysZero` documents a bug that commit `74b19e0` already fixed, and can only `t.Logf`. `TestContract_TimeoutGoroutineNotCancelled` is a bare `t.Log`. `TestContract_DestructiveCapabilityDisabledByDefault` re-implements `risk != "destructive"` inside the test and asserts on its own local variable — it would pass if the handler were deleted. +- **evidence:** `internal/execution/contract_test.go:212-299`. +- **impact:** the suite's green status overstates coverage; a misnamed test actively misleads about a fixed bug. +- **action:** **cleanup.** Delete the three; the real coverage gap is an end-to-end execute over a provider-registered capability. + +### 6. Read-only failures are reported as `unknown`, and timed-out provider calls leak + +- **problem:** `runWithTimeout` (`engine.go:161-186`) can't cancel — `Provider.Execute` takes no `context`. The goroutine runs to the HTTP client's 60s timeout, outliving the 30s capability timeout. +- **evidence:** a POST of a `read`/`read_only` capability against the live service while workspace-mcp was down (`:9930` → 503) hung past 5s. Per spec §4.3 that lands `outcome=unknown` — "may or may not have completed the side effect" — for a call that by definition has none, and `unknown` is never retried. +- **impact:** read failures are unretryable and indistinguishable from genuinely ambiguous mutations. +- **action:** **refactor.** Add `ctx` to the `Provider` interface (2 implementations), and let `read_only=true` timeouts resolve to `failed`. + +### 7. Race: the one-in-flight-per-(capability,target) guard is check-then-insert + +- **problem:** `engine.go:56-58` queries, then `engine.go:92` inserts, with no uniqueness constraint between them. +- **evidence:** no partial unique index on `(capability_id, target_entity_id)` where `status='started'`; the existing `idx_executions_inflight` is non-unique (`sqlite.go:561`). +- **impact:** two concurrent requests both proceed. Narrow window, single writer, low likelihood — but the spec states it as a hard guard and the existing test only covers the sequential case. +- **action:** **repair**, via a partial unique index. + +### 8. Deployed binary is older than HEAD; registered capabilities never refresh + +- **problem:** `cmd/hexisd/main.go:80-83` skips any capability whose ID already exists, so allowlist edits to risk/target_type/enabled never propagate to an existing database. +- **evidence:** live GET returns no `enabled` field → pre-`89d8433` build. Rows dated 2026-07-19. +- **impact:** config drift; even the correct fix for (1) won't reach the running instance's existing rows. +- **action:** **repair.** Reconcile on startup (update when the derived record differs), and redeploy. + +--- + +## keep + +Confirmation lifecycle and its tests; the storage layer's explicitness; `timeout ⇒ unknown`; the provider registry (2 real implementations justify it); the allowlist-as-config design; `nexusclient`; pure-Go sqlite + distroless build. + +## remove + +- `internal/provider/systemd.go` — no `systemctl` in the distroless image and no capability ever registers it; it's the spec's §4.2 `systemd_dbus` slot filled with a shell-adjacent stub. Delete, or replace with D-Bus when a real need appears. +- `Server.ListenUnix` (dead; never called). +- `schema_migrations` table (unused; `PRAGMA user_version` is the real mechanism). **Caveat:** confirm no already-deployed database depends on it before dropping — finding 8 establishes the live instance is several commits behind, so its schema state is not HEAD's. +- `execution.ResolveRequest` / `ResolveResult` + `MarshalJSON` (superseded by `nexusclient`). +- `Engine.emitEvent`, `Registry.List`, `DiscoveredTools`, `Capability.IsDestructive`, `ifString`. +- `EventCapabilityUnavailable`, `EventExecutionDenied`, `ExecutionDenied`, and 8 unused `Err*` values. +- The three vacuous contract tests. + +## repair now + +1. Auth on `/api/v1/` + server-derived `requires_confirmation` (finding 2) — **before anything else**. +2. `Enabled` / `TimeoutSeconds` / `RequiresConfirmation` in `BuildCapabilities`, plus startup reconciliation (1, 8). +3. Target validation against Nexus + `TargetTypes` (3). +4. Persist/read `confirmation_id`, `causation_id`, `capability_version` (4). +5. Partial unique index for in-flight (7). +6. Delete the dead code and misleading tests (5, remove). + +## refactor later + +- `context` through `Provider.Execute` (6). +- Collapse the four competing wire shapes for a capability — `handler.go:96-108` (which emits both `id` and `capability_id`), `adapter.go:147-157`, `pkg/client`, and Maven's vendored copy — into one serializer. +- `GET /api/v1/executions?entity_id=&since=` (spec §4.5, Command Center needs it). +- A `Makefile` and a README: there is no onboarding doc at all. + +## rewrite only if + +Nexus gains blessing and versioned capabilities `(capability_id, version)` as the primary key, *and* multi-writer access becomes real. Neither is true; incremental repair is clearly cheaper. The confirmation logic is the hidden knowledge here and it is correct — do not throw it away. + +## verification plan + +- `go build ./... && go vet ./... && go test ./...` — all currently pass. `gofmt -l` flags `internal/provider/systemd.go`, `internal/provider/workspace_mcp.go`, `pkg/client/client.go` as unformatted at HEAD. +- New tests, each pinning a finding: + - execute a `BuildCapabilities`-registered capability end-to-end (1) + - unauthenticated `/api/v1/execute` rejected (2) + - non-`ent_` / unknown target rejected (3) + - round-trip `confirmation_id` through `GET /executions/{id}` (4) + - concurrent duplicate execute → exactly one row (7) +- Live: rebuild, redeploy, `curl` a read capability once workspace-mcp is back (it is 503 as of this review), confirm `succeeded` and non-empty `changes`. + +## uncertainties + +- **Unknown:** whether `hexis.kvmx.ru:80` is actually resolvable from outside — the nginx config was read, external reachability was not tested. The severity of finding 2 depends on this; the missing-auth defect stands either way. +- **Unknown:** whether blessing is planned for Nexus soon, or whether the target-existence check in finding 3 is the intended permanent guard. +- **Assumption:** Maven is the only consumer. Based on a repo-wide grep of `/home/kami/apps`; an out-of-tree MCP client would not appear. +- **Confirmed but unexplained:** the live instance is ~4 commits behind. Unclear whether that is a stalled deploy or deliberate. + +--- + +## second-reviewer verification (2026-07-30) + +An independent pass re-checked the load-bearing claims against the code at HEAD rather than accepting them. **The verdict and all eight findings stand.** Confirmed directly: + +- **Finding 1** — `workspace_mcp.go:235-249` omits `Enabled` and `TimeoutSeconds`; `engine.go:42` rejects on `!capability.Enabled`. The execution path is dead as described. +- **Finding 2** — no `auth`, `token`, or `Authorization` handling exists anywhere under `internal/api/`. The `hexis.kvmx.ru:80` server block proxies straight to `127.0.0.1:9741` with only `X-Forwarded-*` headers set and no auth directive. +- **Finding 4** — verified by comparing the INSERT column list against all three SELECT column lists; `confirmation_id` is write-only. `causation_id` is absent from the `executions` DDL. +- **Finding 5** — the three tests are as characterised. `TestContract_DestructiveCapabilityDisabledByDefault` computes `enabled := risk != "destructive"` as a local variable and asserts on that, touching no production code. `TestContract_TimeoutGoroutineNotCancelled` is a single `t.Log` with no assertions. +- **Finding 7** — `sqlite.go:143` creates `idx_executions_inflight` as a plain `CREATE INDEX`, not `CREATE UNIQUE INDEX`, and there is no partial unique index anywhere. +- **Finding 8** — `cmd/hexisd/main.go:76-79` `continue`s on any capability whose ID already resolves, so derived changes never reconcile. +- **Verification plan** — `gofmt -l` flags exactly the three files named. + +Amendments made above, none of which change a conclusion: finding 2's description of `enabled` was corrected (it is a caller-supplied override of a correct server default, not "forced true"), finding 4's line references were corrected to the actual INSERT/SELECT sites, and the `schema_migrations` removal gained a deployed-schema caveat. + +One emphasis difference: finding 4 reads as more serious than "silent, not a crash" suggests. The confirmation lifecycle is the part of this codebase the review rightly calls correct and worth keeping — but with `confirmation_id` unreadable, that correctness cannot be demonstrated after the fact from the API. A guard you cannot audit is hard to trust under incident review, which raises finding 4 close to the priority of the target-validation work in finding 3. + +Agreement on sequencing is unqualified; see below. + +## note on sequencing + +Findings 1 and 2 are coupled. Fixing the dead-capability bug without adding auth and confirmation defaults would turn a broken execution path into a remotely reachable one. They should land together as one coherent change.