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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uea55zaiWuEByEDC4UBSdd
16 KiB
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
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 setsEnabled, so it defaults tofalse. - evidence:
internal/provider/workspace_mcp.go:235-249— thedomain.Capabilityliteral omits bothEnabledandTimeoutSeconds, 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 printedEnabled=false RequiresConfirmation=false TimeoutSeconds=0.internal/execution/engine.go:41then returnsErrCapabilityDisabled→ 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
Enabledfrom risk tier andTimeoutSecondsexplicitly. Must not land alone — see finding 2.
2. Fixing (1) naively arms unconfirmed docker stop/restart on a publicly-proxied, unauthenticated port
- problem:
BuildCapabilitiesalso leavesRequiresConfirmation=falseforrisk: mediumdocker start/stop/restart, and the HTTP API has no authentication whatsoever. - evidence:
Maven/deploy/ecosystem/nginx.conf:32-42proxieshexis.kvmx.ru:80→127.0.0.1:9741with no auth block;internal/api/handler.go:37exposesPOST /api/v1/execute;handler.go:115-166lets any callerPOST /api/v1/capabilitieswithprovider+operationof their choosing. Precisely:handler.go:142derives the correct spec §4.3 default (enabled := req.Risk != "destructive"), and then:143-145lets an optionalenabledfield 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}thenPOST /api/v1/execute. The allowlist blocks that specific one (workspace_mcp.go:129-135re-checksssh.execis disabled) — butdocker.stop_containeris allowed, and self-registration bypassesrequires_confirmationentirely. - impact: remote unauthenticated container control.
- action: security, blocking. Require a shared token on
/api/v1/; deriverequires_confirmationfrom risk tier server-side; and stop honouringenabledfrom 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_idis accepted as any non-empty string. Nothing checks theent_shape, existence in Nexus, orcapability.TargetTypes. - evidence:
handler.go:213-216checks non-empty only;engine.go:44-46only compares against a pinnedTargetEntityID, 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 aTargetTypesmatch, before creating the execution. - note: the spec's stronger blessing guard (§4.3) is not implementable today —
grep -rni blessacrossnexus/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_idis INSERTed but absent from every SELECT, so it always reads back empty.causation_idisn't in theexecutionsschema at all, yetengine.go:79sets it on the struct — the field is populated in memory with nowhere to land. - evidence:
confirmation_idappears in the INSERT atsqlite.go:295and in none of the three executions SELECTs (:313,:369,:418).causation_idexists only onhexis_events(:119,:502), never onexecutions. Also missing vs spec §4.1:capability_versionon executions (it exists onconfirmationsat: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_ChangesSinceAlwaysZerodocuments a bug that commit74b19e0already fixed, and can onlyt.Logf.TestContract_TimeoutGoroutineNotCancelledis a baret.Log.TestContract_DestructiveCapabilityDisabledByDefaultre-implementsrisk != "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.Executetakes nocontext. The goroutine runs to the HTTP client's 60s timeout, outliving the 30s capability timeout. - evidence: a POST of a
read/read_onlycapability against the live service while workspace-mcp was down (:9930→ 503) hung past 5s. Per spec §4.3 that landsoutcome=unknown— "may or may not have completed the side effect" — for a call that by definition has none, andunknownis never retried. - impact: read failures are unretryable and indistinguishable from genuinely ambiguous mutations.
- action: refactor. Add
ctxto theProviderinterface (2 implementations), and letread_only=truetimeouts resolve tofailed.
7. Race: the one-in-flight-per-(capability,target) guard is check-then-insert
- problem:
engine.go:56-58queries, thenengine.go:92inserts, with no uniqueness constraint between them. - evidence: no partial unique index on
(capability_id, target_entity_id)wherestatus='started'; the existingidx_executions_inflightis 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-83skips 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
enabledfield → pre-89d8433build. 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— nosystemctlin the distroless image and no capability ever registers it; it's the spec's §4.2systemd_dbusslot filled with a shell-adjacent stub. Delete, or replace with D-Bus when a real need appears.Server.ListenUnix(dead; never called).schema_migrationstable (unused;PRAGMA user_versionis 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 bynexusclient).Engine.emitEvent,Registry.List,DiscoveredTools,Capability.IsDestructive,ifString.EventCapabilityUnavailable,EventExecutionDenied,ExecutionDenied, and 8 unusedErr*values.- The three vacuous contract tests.
repair now
- Auth on
/api/v1/+ server-derivedrequires_confirmation(finding 2) — before anything else. Enabled/TimeoutSeconds/RequiresConfirmationinBuildCapabilities, plus startup reconciliation (1, 8).- Target validation against Nexus +
TargetTypes(3). - Persist/read
confirmation_id,causation_id,capability_version(4). - Partial unique index for in-flight (7).
- Delete the dead code and misleading tests (5, remove).
refactor later
contextthroughProvider.Execute(6).- Collapse the four competing wire shapes for a capability —
handler.go:96-108(which emits bothidandcapability_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
Makefileand 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 -lflagsinternal/provider/systemd.go,internal/provider/workspace_mcp.go,pkg/client/client.goas unformatted at HEAD.- New tests, each pinning a finding:
- execute a
BuildCapabilities-registered capability end-to-end (1) - unauthenticated
/api/v1/executerejected (2) - non-
ent_/ unknown target rejected (3) - round-trip
confirmation_idthroughGET /executions/{id}(4) - concurrent duplicate execute → exactly one row (7)
- execute a
- Live: rebuild, redeploy,
curla read capability once workspace-mcp is back (it is 503 as of this review), confirmsucceededand non-emptychanges.
uncertainties
- Unknown: whether
hexis.kvmx.ru:80is 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-249omitsEnabledandTimeoutSeconds;engine.go:42rejects on!capability.Enabled. The execution path is dead as described. - Finding 2 — no
auth,token, orAuthorizationhandling exists anywhere underinternal/api/. Thehexis.kvmx.ru:80server block proxies straight to127.0.0.1:9741with onlyX-Forwarded-*headers set and no auth directive. - Finding 4 — verified by comparing the INSERT column list against all three SELECT column lists;
confirmation_idis write-only.causation_idis absent from theexecutionsDDL. - Finding 5 — the three tests are as characterised.
TestContract_DestructiveCapabilityDisabledByDefaultcomputesenabled := risk != "destructive"as a local variable and asserts on that, touching no production code.TestContract_TimeoutGoroutineNotCancelledis a singlet.Logwith no assertions. - Finding 7 —
sqlite.go:143createsidx_executions_inflightas a plainCREATE INDEX, notCREATE UNIQUE INDEX, and there is no partial unique index anywhere. - Finding 8 —
cmd/hexisd/main.go:76-79continues on any capability whose ID already resolves, so derived changes never reconcile. - Verification plan —
gofmt -lflags 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.