Work stops here — the deploy is blocked on Maven, which is under construction. Records what shipped, the five behavioural changes to read before deploying (notably that hexisd now refuses to start without a token and that execute requires Nexus liveness), what is blocked versus doable now, and the limitations accepted along the way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uea55zaiWuEByEDC4UBSdd
6.7 KiB
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)
hexisdrefuses to start withoutHEXIS_API_TOKEN. Fails closed twice:log.Fatalfbefore listening, and the middleware returns 503 rather than ever serving unauthenticated./healthand/readystay open.- Execute now requires Nexus liveness. An unreachable Nexus means 503
ErrTargetUnverifiable, not a permitted execution. This dependency did not exist before. enabledandrequires_confirmationare no longer accepted from the request body at all — both derived from the risk tier server-side.- The 11 → 12 migration writes to existing rows. Duplicate in-flight rows
are rewritten to
status='unknown'(lowestidper pair kept), because the new partial UNIQUE index cannot be created otherwise. Check first:SELECT capability_id, target_entity_id, COUNT(*) FROM executions WHERE status='started' GROUP BY 1,2 HAVING COUNT(*)>1; - Capabilities now execute at all — 14
read+ 1lowenabled without confirmation, 3mediumdocker mutations enabled with confirmation.ssh.execremains 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 atMaven/vendor/github.com/kami/hexis/pkg/client/client.gopredates evenWithToken). - Maven:
cmd/mavend/ecosystem.go:322→hexisclient.New(cfg.Hexis.URL).WithToken(cfg.Hexis.Token), plus aTokenfield on the Hexis config block and the token in deploy config. - Hexis:
HEXIS_API_TOKENinMaven/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-composeusesbuild: ../../../hexiswith no tag or digest, somake compose-upships whatever is in the working tree. - Then verify live: a read capability returns
succeededwith non-emptychanges.
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-42is a template with a wildcardlisten 80;and no ACL. The live config is correctly locked to WireGuard + LAN withdeny all, but anyone following the repo's own instructions would produce the internet-exposed config the review described. Also: public DNS forhexis./maven./nexus./vikunja..kvmx.ruresolves to109.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 in47be24cstops 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
idandcapability_idare emitted, carrying the same value. Kept deliberately so Maven does not take a second breaking change alongside auth. Retiringidis an announced removal, not a tidy-up; a test says so. schema_migrationswas not dropped despite the review's remove list.migrate()applies migrations by slice index and writesuser_version = index + 1, so removing an element renumbers every later migration and any database past that point would permanently skip one. TheCREATE TABLEis 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.