feat(ecosystem): compliant Praxis/Hexis integration + vendored build

Bring the Nexus/Praxis/Hexis integration in line with
MAVEN_ECOSYSTEM_ARCHITECTURE.md:

- Praxis over HTTP: drop the in-process praxis.db open (praxisstore/
  praxistools) and call praxisd's /api/v1/tools/* API via a new praxisClient.
  Honors the "no component reads another's DB" invariant (AC#12).
  PraxisConfig.DBPath -> URL.
- Hexis confirmation gate: mutating capabilities (ReadOnly=false) now park a
  bound pendingHexis confirmation and require a spoken "да" before executing;
  read-only run immediately (AC#7, no auto attention->action).
- Capability safety: >1 verb match is ambiguous -> ask instead of firing the
  first; ambiguous Nexus resolution asks for clarification (AC#2).
- Correlation IDs on Hexis execute, recorded in the cross-service trace.
- Bug: importance arrives as JSON float64 over HTTP, not int.
- Tests: confirm-gate, decline, read-only, and ambiguity paths.

Build: vendor/ bakes in the hexis client (replace-directed at a sibling repo
outside the Docker context); Dockerfile builds from vendor and no longer
`go mod download`s the unreachable replace paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kami
2026-07-19 19:32:05 +04:00
parent 0c65387a5f
commit 6c92f85d10
2071 changed files with 3915438 additions and 61 deletions
+157
View File
@@ -0,0 +1,157 @@
package client
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type Client struct {
baseURL string
httpClient *http.Client
}
func New(baseURL string) *Client {
return &Client{
baseURL: baseURL,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
}
func (c *Client) do(ctx context.Context, method, path string, body, result any) error {
var reqBody io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("marshal request: %w", err)
}
reqBody = bytes.NewReader(data)
}
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, reqBody)
if err != nil {
return fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
var errResp struct {
Error string `json:"error"`
}
json.NewDecoder(resp.Body).Decode(&errResp)
if errResp.Error != "" {
return fmt.Errorf("%s: %s", http.StatusText(resp.StatusCode), errResp.Error)
}
return fmt.Errorf("%s", http.StatusText(resp.StatusCode))
}
if result != nil {
return json.NewDecoder(resp.Body).Decode(result)
}
return nil
}
type Capability struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
TargetTypes []string `json:"target_types"`
TargetEntityID string `json:"target_entity_id,omitempty"`
Provider string `json:"provider"`
Operation string `json:"operation"`
Risk string `json:"risk,omitempty"`
ReadOnly bool `json:"read_only"`
ExpectedSideEffects string `json:"expected_side_effects,omitempty"`
}
type Execution struct {
ID string `json:"id"`
CapabilityID string `json:"capability_id"`
TargetEntityID string `json:"target_entity_id"`
Status string `json:"status"`
Result map[string]any `json:"result,omitempty"`
Error string `json:"error,omitempty"`
RequestedBy map[string]string `json:"requested_by,omitempty"`
CorrelationID string `json:"correlation_id,omitempty"`
IdempotencyKey string `json:"idempotency_key,omitempty"`
}
type ExecuteRequest struct {
CapabilityID string `json:"capability_id"`
TargetEntityID string `json:"target_entity_id"`
Arguments map[string]any `json:"arguments,omitempty"`
RequestedBy map[string]string `json:"requested_by,omitempty"`
Origin map[string]string `json:"origin,omitempty"`
IdempotencyKey string `json:"idempotency_key,omitempty"`
CorrelationID string `json:"correlation_id,omitempty"`
}
type CreateCapabilityRequest struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
TargetTypes []string `json:"target_types"`
TargetEntityID string `json:"target_entity_id,omitempty"`
Provider string `json:"provider"`
Operation string `json:"operation"`
Risk string `json:"risk,omitempty"`
ReadOnly bool `json:"read_only"`
ExpectedSideEffects string `json:"expected_side_effects,omitempty"`
}
func (c *Client) Capabilities(ctx context.Context, entityID string) ([]Capability, error) {
var result []Capability
path := "/api/v1/capabilities"
if entityID != "" {
path += "?entity_id=" + entityID
}
if err := c.do(ctx, http.MethodGet, path, nil, &result); err != nil {
return nil, err
}
return result, nil
}
func (c *Client) GetCapability(ctx context.Context, id string) (*Capability, error) {
var result Capability
if err := c.do(ctx, http.MethodGet, "/api/v1/capabilities/"+id, nil, &result); err != nil {
return nil, err
}
return &result, nil
}
func (c *Client) CreateCapability(ctx context.Context, req CreateCapabilityRequest) (*Capability, error) {
var result Capability
if err := c.do(ctx, http.MethodPost, "/api/v1/capabilities", req, &result); err != nil {
return nil, err
}
return &result, nil
}
func (c *Client) Execute(ctx context.Context, req ExecuteRequest) (*Execution, error) {
var result Execution
if err := c.do(ctx, http.MethodPost, "/api/v1/execute", req, &result); err != nil {
return nil, err
}
return &result, nil
}
func (c *Client) GetExecution(ctx context.Context, id string) (*Execution, error) {
var result Execution
if err := c.do(ctx, http.MethodGet, "/api/v1/executions/"+id, nil, &result); err != nil {
return nil, err
}
return &result, nil
}
func (c *Client) Health(ctx context.Context) error {
return c.do(ctx, http.MethodGet, "/health", nil, nil)
}