Files
hexis/HANDOFF.md
kami 66f5c397f7 Add handoff notes: state, deploy blockers, next steps
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
2026-07-31 00:46:43 +04:00

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)

  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:
    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:322hexisclient.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 354Maven/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.