dda4acfbb6
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
55 lines
1.7 KiB
Go
55 lines
1.7 KiB
Go
// Package wire converts internal domain objects into the public wire shapes
|
|
// defined in pkg/client. It is the single serialization point: the HTTP
|
|
// handler and the MCP adapter both go through it, so a capability looks the
|
|
// same on every surface Hexis exposes.
|
|
package wire
|
|
|
|
import (
|
|
"github.com/kami/hexis/internal/domain"
|
|
"github.com/kami/hexis/pkg/client"
|
|
)
|
|
|
|
// Capability converts a domain capability into the public wire shape.
|
|
//
|
|
// Both `capability_id` and `id` are populated with the same value; see the
|
|
// compatibility note on client.Capability for why the alias is retained.
|
|
func Capability(c *domain.Capability) client.Capability {
|
|
if c == nil {
|
|
return client.Capability{}
|
|
}
|
|
targetTypes := c.TargetTypes
|
|
if targetTypes == nil {
|
|
targetTypes = []string{}
|
|
}
|
|
return client.Capability{
|
|
CapabilityID: c.ID,
|
|
ID: c.ID,
|
|
Name: c.Name,
|
|
Description: c.Description,
|
|
TargetTypes: targetTypes,
|
|
TargetEntityID: c.TargetEntityID,
|
|
Provider: c.Provider,
|
|
Operation: c.Operation,
|
|
Risk: c.Risk,
|
|
ReadOnly: c.ReadOnly,
|
|
ExpectedSideEffects: c.ExpectedSideEffects,
|
|
RequiresConfirmation: c.RequiresConfirmation,
|
|
Enabled: c.Enabled,
|
|
TimeoutSeconds: c.TimeoutSeconds,
|
|
Attributes: c.Attributes,
|
|
CreatedAt: c.CreatedAt,
|
|
UpdatedAt: c.UpdatedAt,
|
|
Version: c.Version,
|
|
}
|
|
}
|
|
|
|
// Capabilities converts a slice, never returning nil so the JSON encoding is
|
|
// `[]` rather than `null`.
|
|
func Capabilities(caps []*domain.Capability) []client.Capability {
|
|
out := make([]client.Capability, 0, len(caps))
|
|
for _, c := range caps {
|
|
out = append(out, Capability(c))
|
|
}
|
|
return out
|
|
}
|