c7325a20d4
Findings 1 and 2 of REVIEW-2026-07-30.md, which must land together: every workspace capability registered with enabled=false, so the only working provider could never execute. Fixing that alone would have turned a dead execution path into a reachable one on an unauthenticated port. Auth: a shared bearer token (HEXIS_API_TOKEN) is now required on the whole /api/v1/ surface, compared with crypto/subtle.ConstantTimeCompare. /health and /ready stay open for probes. It fails closed twice over — hexisd refuses to start with an empty token, and the middleware returns 503 rather than ever serving unauthenticated. Guards: `enabled` and `requires_confirmation` are no longer readable from the request body at all. Previously the handler derived the correct §4.3 default and then let the caller override it, which is worse than no guard because it reads as enforced. Both are now derived from the risk tier by shared helpers in domain, used by the HTTP and provider registration paths alike; unrecognised tiers fail closed to requiring confirmation. BuildCapabilities sets Enabled, RequiresConfirmation and TimeoutSeconds explicitly, and hexisd reconciles drifted rows on startup instead of skipping any capability whose ID already exists — without that, allowlist edits never reach an existing database. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uea55zaiWuEByEDC4UBSdd
128 lines
5.5 KiB
Go
128 lines
5.5 KiB
Go
package domain
|
|
|
|
import "time"
|
|
|
|
type ExecuteRequest struct {
|
|
CapabilityID string `json:"capability_id"`
|
|
TargetEntityID string `json:"target_entity_id"`
|
|
EntityVersion int64 `json:"entity_version,omitempty"`
|
|
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"`
|
|
CausationID string `json:"causation_id,omitempty"`
|
|
ResolutionEvidence []map[string]any `json:"resolution_evidence,omitempty"`
|
|
ConfirmationID string `json:"confirmation_id,omitempty"`
|
|
}
|
|
|
|
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"`
|
|
RequiresConfirmation bool `json:"requires_confirmation"`
|
|
Enabled bool `json:"enabled"`
|
|
TimeoutSeconds int `json:"timeout_seconds,omitempty"`
|
|
Attributes map[string]any `json:"attributes,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
Version int64 `json:"version"`
|
|
}
|
|
|
|
// Risk tiers. ECOSYSTEM-SPEC.md §4.1 names read/low/protected/destructive; the
|
|
// workspace allowlist additionally uses medium/high, which map onto the same
|
|
// two guard decisions below.
|
|
const (
|
|
RiskRead = "read"
|
|
RiskLow = "low"
|
|
RiskMedium = "medium"
|
|
RiskProtected = "protected"
|
|
RiskHigh = "high"
|
|
RiskDestructive = "destructive"
|
|
)
|
|
|
|
// DefaultCapabilityTimeoutSeconds is the per-capability wall-clock timeout
|
|
// applied when a registration does not specify one (ECOSYSTEM-SPEC.md §4.3).
|
|
const DefaultCapabilityTimeoutSeconds = 30
|
|
|
|
// EnabledForRisk reports the server-derived `enabled` value for a risk tier.
|
|
// Destructive capabilities are disabled by default and must be turned on
|
|
// explicitly out of band (ECOSYSTEM-SPEC.md §4.3). This is never overridable
|
|
// by an API caller.
|
|
func EnabledForRisk(risk string) bool {
|
|
return risk != RiskDestructive
|
|
}
|
|
|
|
// RequiresConfirmationForRisk reports the server-derived
|
|
// `requires_confirmation` value for a risk tier. Only genuinely read-only and
|
|
// low-risk tiers skip confirmation; unrecognised tiers fail closed.
|
|
func RequiresConfirmationForRisk(risk string) bool {
|
|
switch risk {
|
|
case "", RiskRead, RiskLow:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
type ExecutionStatus string
|
|
|
|
const (
|
|
ExecutionStarted ExecutionStatus = "started"
|
|
ExecutionSucceeded ExecutionStatus = "succeeded"
|
|
ExecutionFailed ExecutionStatus = "failed"
|
|
ExecutionDenied ExecutionStatus = "denied"
|
|
// ExecutionUnknown marks a wall-clock timeout: the executor may or may not
|
|
// have completed the side effect. Never retried automatically.
|
|
ExecutionUnknown ExecutionStatus = "unknown"
|
|
)
|
|
|
|
type Execution struct {
|
|
ID string `json:"id"`
|
|
CapabilityID string `json:"capability_id"`
|
|
TargetEntityID string `json:"target_entity_id"`
|
|
EntityVersion int64 `json:"entity_version,omitempty"`
|
|
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"`
|
|
ConfirmationID string `json:"confirmation_id,omitempty"`
|
|
Status ExecutionStatus `json:"status"`
|
|
Result map[string]any `json:"result,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
ResolutionEvidence []map[string]any `json:"resolution_evidence,omitempty"`
|
|
CorrelationID string `json:"correlation_id,omitempty"`
|
|
CausationID string `json:"causation_id,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type HexisEventType string
|
|
|
|
const (
|
|
EventCapabilityRegistered HexisEventType = "hexis.capability.registered"
|
|
EventCapabilityUnavailable HexisEventType = "hexis.capability.unavailable"
|
|
EventExecutionStarted HexisEventType = "hexis.execution.started"
|
|
EventExecutionSucceeded HexisEventType = "hexis.execution.succeeded"
|
|
EventExecutionFailed HexisEventType = "hexis.execution.failed"
|
|
EventExecutionDenied HexisEventType = "hexis.execution.denied"
|
|
)
|
|
|
|
type Event struct {
|
|
ID string `json:"id"`
|
|
Sequence int64 `json:"sequence"`
|
|
Type HexisEventType `json:"type"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Actor string `json:"actor,omitempty"`
|
|
CorrelationID string `json:"correlation_id,omitempty"`
|
|
CausationID string `json:"causation_id,omitempty"`
|
|
Payload map[string]any `json:"payload,omitempty"`
|
|
}
|