Serve capabilities through one serializer and add GET /api/v1/executions
The two "refactor later" items from REVIEW-2026-07-30.md; they share the wire types, so they land together. A capability had four divergent wire shapes — the HTTP handler, the MCP adapter, pkg/client, and Maven's vendored copy of it. There is now a single definition in pkg/client, mapped from domain by internal/wire and used by the HTTP list/create/get paths and all four MCP surfaces. It lives in pkg/client rather than internal so external consumers need not vendor internal/domain, and so producer and consumer are literally the same type. The unified shape is a strict superset of all four predecessors; nothing was dropped. It adds enabled and requires_confirmation to the list responses (never omitempty — an absent bool reads as unknown, not false), capability_id to the MCP and client shapes, and the timing/attribute/version fields previously only on get-by-ID. target_types and the list itself now serialize as [] rather than null. Both `id` and `capability_id` are deliberately kept, carrying the same value. Maven decodes `id`; the spec and the rest of the API say `capability_id`. Bearer auth is already a breaking change for that consumer, and stacking a second silent one is the wrong trade — the redundancy stays until every consumer is confirmed on capability_id, then `id` goes in an announced removal. A test pins this and says so. GET /api/v1/executions?entity_id=&since=&limit= implements spec §4.5, which the Command Center needs. `since` reuses the changes-feed cursor convention rather than inventing a second paging idiom. That cursor is the row's implicit SQLite rowid, which is safe only while nothing deletes executions and nothing VACUUMs — both would renumber and silently invalidate outstanding cursors. If retention is ever added, this must become an explicit monotonic column first; the constraint is documented at the query site. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uea55zaiWuEByEDC4UBSdd
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package client
|
||||
|
||||
import "time"
|
||||
|
||||
// Capability is THE wire shape for a Hexis capability.
|
||||
//
|
||||
// There is exactly one definition of it, here, and every producer in this
|
||||
// repository serializes through it: the HTTP handler (GET/POST
|
||||
// /api/v1/capabilities, GET /api/v1/capabilities/{id}), the MCP adapter
|
||||
// (hexis.list_capabilities), and this client's decode path. It lives in
|
||||
// pkg/client rather than internal/ so that external consumers get the shape
|
||||
// without vendoring internal packages; internal/wire holds the
|
||||
// domain.Capability -> Capability conversion.
|
||||
//
|
||||
// Compatibility note — `id` and `capability_id` are BOTH emitted, deliberately.
|
||||
// They always carry the same value. Maven's vendored consumer decodes `id`
|
||||
// (cmd/mavend/voice.go matches on Capability.ID); the ECOSYSTEM-SPEC.md §4.1
|
||||
// schema and the rest of the Hexis API name the column `capability_id`. Hexis
|
||||
// is mid-rollout of bearer auth on /api/v1/, which is already one breaking
|
||||
// change for that consumer; dropping either alias here would stack a second,
|
||||
// silent one on top. Both stay until every consumer is confirmed to read
|
||||
// `capability_id`, at which point `id` can be removed in a deliberate,
|
||||
// announced change. Do not "clean this up" incidentally.
|
||||
type Capability struct {
|
||||
// CapabilityID is the canonical field (ECOSYSTEM-SPEC.md §4.1).
|
||||
CapabilityID string `json:"capability_id"`
|
||||
// ID is a deprecated alias for CapabilityID, kept for wire compatibility.
|
||||
// Always identical to CapabilityID. Prefer CapabilityID in new code.
|
||||
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 and Enabled are server-derived from the risk tier
|
||||
// and are never settable by a caller. listCapabilities used to omit both,
|
||||
// which left clients unable to tell a callable capability from one that
|
||||
// would be rejected with 403; the unified shape always carries them.
|
||||
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,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
Version int64 `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// EffectiveID returns the capability ID, tolerating a peer that sends only one
|
||||
// of the two aliases.
|
||||
func (c Capability) EffectiveID() string {
|
||||
if c.CapabilityID != "" {
|
||||
return c.CapabilityID
|
||||
}
|
||||
return c.ID
|
||||
}
|
||||
Reference in New Issue
Block a user