# Hexis — handoff Date: 2026-07-31 State: all code work from `REVIEW-2026-07-30.md` is **committed and pushed**; **nothing is deployed**. Branch `master`, remote `origin` = `ssh://git@192.168.1.104:2222/kami/hexis`. Stopped here deliberately: the deploy is blocked on Maven, which is under construction. --- ## What shipped Eight commits, `945e4ba..ae6bd08`. All eight review findings are closed. | commit | what | findings | |---|---|---| | `c7325a2` | Auth on `/api/v1/` + server-derived capability guards | 1, 2 | | `d428560` | Execution audit fields + in-flight guard as a SQL constraint | 4, 7 | | `47be24c` | Target validation against Nexus | 3 | | `be08938` | `context` through `Provider.Execute`; read-only timeouts | 6 | | `9e6b995` | Dead code + three tests that assert nothing | 5 | | `dda4acf` | One capability serializer + `GET /api/v1/executions` | §4.5, wire shapes | | `3ac0fbb` | gofmt realignment | — | | `ae6bd08` | Makefile, README, the review itself | — | Verified at HEAD: `go build`, `go vet`, `go test ./...`, `go test -race ./...` all pass; `gofmt -l` clean; a fresh database migrates to the final `user_version` and reopening is a no-op. **Intermediate commits are not individually compile-verified — only HEAD is.** Several hunks span findings (one 124-line `engine.go` hunk covers both target validation and a dead-code import removal), so whole hunks were assigned to their dominant commit rather than hand-split. Concretely, `c7325a2` also carries the `listCapabilities` serializer change and registers the `/executions` route whose handler does not arrive until `dda4acf`. `git bisect` across the middle of this range may not build. ## What changed behaviourally (read before deploying) 1. **`hexisd` refuses to start without `HEXIS_API_TOKEN`.** Fails closed twice: `log.Fatalf` before listening, and the middleware returns 503 rather than ever serving unauthenticated. `/health` and `/ready` stay open. 2. **Execute now requires Nexus liveness.** An unreachable Nexus means 503 `ErrTargetUnverifiable`, not a permitted execution. This dependency did not exist before. 3. **`enabled` and `requires_confirmation` are no longer accepted from the request body at all** — both derived from the risk tier server-side. 4. **The 11 → 12 migration writes to existing rows.** Duplicate in-flight rows are rewritten to `status='unknown'` (lowest `id` per pair kept), because the new partial UNIQUE index cannot be created otherwise. Check first: ```sql SELECT capability_id, target_entity_id, COUNT(*) FROM executions WHERE status='started' GROUP BY 1,2 HAVING COUNT(*)>1; ``` 5. Capabilities now execute at all — 14 `read` + 1 `low` enabled without confirmation, 3 `medium` docker mutations enabled *with* confirmation. `ssh.exec` remains allowlist-skipped. ## Next steps, in order ### 1. Blocked on Maven — the deploy (Vikunja 355) Cannot proceed while Maven is under construction. When it resumes, these ship **together** or voice execution breaks: - Maven: `go mod vendor` (the vendored client at `Maven/vendor/github.com/kami/hexis/pkg/client/client.go` predates even `WithToken`). - Maven: `cmd/mavend/ecosystem.go:322` → `hexisclient.New(cfg.Hexis.URL).WithToken(cfg.Hexis.Token)`, plus a `Token` field on the Hexis config block and the token in deploy config. - Hexis: `HEXIS_API_TOKEN` in `Maven/deploy/ecosystem/docker-compose.yml`. - Bring up workspace-mcp (container is `Exited (0)`, nothing on `:9930`) and confirm Nexus is up on `:9740`. - Rebuild **from a commit**. The running image was built 2026-07-19 21:14, ~3.5h before the first commit existed — it has never corresponded to any revision. `docker-compose` uses `build: ../../../hexis` with no tag or digest, so `make compose-up` ships whatever is in the working tree. - Then verify live: a read capability returns `succeeded` with non-empty `changes`. Task 343's serializer change does **not** break Maven — it reads only `.ID`, `.Name`, `.Description`, `.ReadOnly`, all unchanged, and `id` is still on the wire. The only break is auth. ### 2. Not blocked — can be done any time - **Vikunja 354** — `Maven/deploy/ecosystem/nginx.conf:32-42` is a template with a wildcard `listen 80;` and no ACL. The *live* config is correctly locked to WireGuard + LAN with `deny all`, but anyone following the repo's own instructions would produce the internet-exposed config the review described. Also: public DNS for `hexis./maven./nexus./vikunja..kvmx.ru` resolves to `109.229.102.117`, which is not this host and is unreachable — stale records, worth cleaning up. - **Vikunja 357 — a decision, not code.** Blessing is specified in `ECOSYSTEM-SPEC.md` (§1.2, §4.3, §7.1, §8 Phase 5, §10 invariant 7) and implemented nowhere in Nexus: no column, no migration, no TODO, no tracker item. What shipped in `47be24c` stops free-text targets but does **not** stop mutation of an auto-discovered entity, which is what blessing exists to prevent. Either blessing lands in Nexus, or the spec gets amended to describe the guard that actually exists. Right now the spec documents a protection that is absent everywhere. ### 3. Latent — a tripwire, not a bug (Vikunja 358) `GET /api/v1/executions?since=` pages on the executions row's implicit SQLite `rowid`. Safe **only** while nothing deletes execution rows and nothing runs `VACUUM`; either renumbers and silently invalidates outstanding cursors, so a polling client would skip or repeat rows with no error. The table grows unboundedly, so retention will eventually be wanted — add an explicit monotonic `seq` column **first**. Documented at the `executionColumns` declaration in `internal/storage/sqlite.go`. ## Known limitations accepted - Rows predating the 11 → 12 migration get `capability_version = 0`, indistinguishable from a genuine 0. Backfill is impossible — the capabilities have since mutated and the executions never recorded what they ran against. - Both `id` and `capability_id` are emitted, carrying the same value. Kept deliberately so Maven does not take a second breaking change alongside auth. Retiring `id` is an announced removal, not a tidy-up; a test says so. - `schema_migrations` was **not** dropped despite the review's remove list. `migrate()` applies migrations by slice index and writes `user_version = index + 1`, so removing an element renumbers every later migration and any database past that point would permanently skip one. The `CREATE TABLE` is now a no-op holding its slot. ## One thing to reconsider `REVIEW-2026-07-30.md` is committed in-tree. It documents the unauthenticated execute attack path and names `hexis.kvmx.ru`. Fine on a LAN Gitea; reconsider before this repo is mirrored anywhere wider.