Pulse/internal/agentcapabilities/surface_contract.go
rcourtman ee8a24e14a backend and governance: MCP contract, agent capabilities, API, and release-control
Manifest-backed MCP tools, prompts, and resources with surface affordance contracts; agent capability manifest and governance projection; API contract tests and capability route projection; operations-loop and intelligence-funnel telemetry; release-control subsystem documentation, registry, and tooling; licensing and configuration.
2026-06-23 17:26:15 +01:00

392 lines
15 KiB
Go

package agentcapabilities
import "strings"
const (
SurfaceIDPulseAssistant = "pulse_assistant"
SurfaceIDPulseMCP = "pulse_mcp"
SurfaceToolSourceAssistantRegistry = "assistant_registry"
SurfaceToolSourceCapabilityManifest = "capability_manifest"
)
// SurfaceToolContract describes the tool vocabulary projected for one
// operator-facing Pulse Intelligence surface. Assistant tools come from the
// native registry/provider-tool projection, while external-adapter tools come
// from the canonical manifest capability projection; keeping the source
// explicit stops supported surfaces from being treated as competing duplicate
// implementations.
type SurfaceToolContract struct {
SurfaceID string `json:"surfaceId"`
SurfaceLabel string `json:"surfaceLabel,omitempty"`
ToolSource string `json:"toolSource"`
ToolNames []string `json:"toolNames"`
RegistryToolNames []string `json:"registryToolNames,omitempty"`
CapabilityNames []string `json:"capabilityNames,omitempty"`
NativeToolNames []string `json:"nativeToolNames,omitempty"`
Affordances SurfaceAffordanceContract `json:"affordances,omitempty"`
}
// ResolvedSurfaceAffordanceContract is the normalized surface identity and
// affordance set that prompts, adapters, docs, and UI projections should use
// when they need one surface's supported Pulse Intelligence affordances.
type ResolvedSurfaceAffordanceContract struct {
SurfaceID string
SurfaceLabel string
Affordances SurfaceAffordanceContract
}
// NormalizeCollections detaches slice fields and preserves a stable empty
// toolNames collection for JSON surfaces.
func (c SurfaceToolContract) NormalizeCollections() SurfaceToolContract {
c.ToolNames = append([]string(nil), c.ToolNames...)
if c.ToolNames == nil {
c.ToolNames = []string{}
}
c.RegistryToolNames = append([]string(nil), c.RegistryToolNames...)
c.CapabilityNames = append([]string(nil), c.CapabilityNames...)
c.NativeToolNames = append([]string(nil), c.NativeToolNames...)
return c
}
// CloneSurfaceToolContracts returns detached surface tool summaries in their
// original projection order.
func CloneSurfaceToolContracts(contracts []SurfaceToolContract) []SurfaceToolContract {
cloned := make([]SurfaceToolContract, 0, len(contracts))
for _, contract := range contracts {
cloned = append(cloned, contract.NormalizeCollections())
}
return cloned
}
// DefaultSurfaceLabelForID returns the compatibility label for known Pulse
// Intelligence surfaces when older manifest payloads omit a surfaceContract
// block.
func DefaultSurfaceLabelForID(surfaceID string) string {
switch strings.TrimSpace(surfaceID) {
case SurfaceIDPulseAssistant:
return "Pulse Assistant"
case SurfaceIDPulseMCP:
return "Pulse MCP"
default:
return ""
}
}
// DefaultSurfaceAffordancesForID returns compatibility defaults for known
// Pulse Intelligence surfaces when older manifest payloads do not declare the
// affordance block.
func DefaultSurfaceAffordancesForID(surfaceID string) SurfaceAffordanceContract {
switch strings.TrimSpace(surfaceID) {
case SurfaceIDPulseAssistant:
return SurfaceAffordanceContract{
Tools: true,
InteractiveQuestions: true,
}
case SurfaceIDPulseMCP:
return SurfaceAffordanceContract{
Tools: true,
Resources: true,
Prompts: true,
CapabilityMetadata: true,
}
default:
return SurfaceAffordanceContract{}
}
}
// SurfaceAffordancesDeclared reports whether any affordance was explicitly
// enabled. It intentionally treats an all-false struct as missing/legacy for
// the known current surfaces, because older JSON payloads had no affordance
// field at all.
func SurfaceAffordancesDeclared(affordances SurfaceAffordanceContract) bool {
return affordances.Tools ||
affordances.Resources ||
affordances.Prompts ||
affordances.CapabilityMetadata ||
affordances.InteractiveQuestions
}
// NormalizeSurfaceAffordances fills compatibility defaults for known surfaces
// while preserving explicit manifest-owned affordances when present.
func NormalizeSurfaceAffordances(surface OperatorSurfaceContract) SurfaceAffordanceContract {
if SurfaceAffordancesDeclared(surface.Affordances) {
return surface.Affordances
}
return DefaultSurfaceAffordancesForID(surface.ID)
}
// ResolveSurfaceAffordanceContract resolves one operator surface from the
// manifest surface contract and applies compatibility defaults for known first-
// party surfaces. The returned bool reports whether the surface was declared in
// the provided contract; callers can still use the returned compatibility
// defaults when false.
func ResolveSurfaceAffordanceContract(contract SurfaceContract, surfaceID, surfaceLabelFallback string) (ResolvedSurfaceAffordanceContract, bool) {
surfaceID = strings.TrimSpace(surfaceID)
if surfaceID == "" {
return ResolvedSurfaceAffordanceContract{}, false
}
lookupLabel := strings.TrimSpace(surfaceLabelFallback)
if lookupLabel == "" {
lookupLabel = DefaultSurfaceLabelForID(surfaceID)
}
label := lookupLabel
if label == "" {
label = surfaceID
}
if surface, ok := FindOperatorSurfaceContract(contract, surfaceID, lookupLabel); ok {
resolvedID := strings.TrimSpace(surface.ID)
if resolvedID == "" {
resolvedID = surfaceID
}
resolvedLabel := strings.TrimSpace(surface.Label)
if resolvedLabel == "" {
resolvedLabel = label
}
return ResolvedSurfaceAffordanceContract{
SurfaceID: resolvedID,
SurfaceLabel: resolvedLabel,
Affordances: NormalizeSurfaceAffordances(surface),
}, true
}
return ResolvedSurfaceAffordanceContract{
SurfaceID: surfaceID,
SurfaceLabel: label,
Affordances: DefaultSurfaceAffordancesForID(surfaceID),
}, false
}
// ManifestSurfaceAffordances resolves the affordance contract for one manifest
// surface. Known first-party surfaces retain compatibility defaults for older
// manifests that predate the surfaceContract block.
func ManifestSurfaceAffordances(manifest Manifest, surfaceID string) (SurfaceAffordanceContract, bool) {
resolution, ok := ResolveSurfaceAffordanceContract(manifest.SurfaceContract, surfaceID, "")
if SurfaceAffordancesDeclared(resolution.Affordances) {
return resolution.Affordances, ok
}
return SurfaceAffordanceContract{}, false
}
// FindOperatorSurfaceContract resolves one manifest-owned operator surface by
// id or label.
func FindOperatorSurfaceContract(contract SurfaceContract, surfaceID, surfaceName string) (OperatorSurfaceContract, bool) {
surfaceID = strings.TrimSpace(surfaceID)
surfaceName = strings.TrimSpace(surfaceName)
for _, surface := range contract.OperatorSurfaces {
if OperatorSurfaceContractMatches(surface, surfaceID, surfaceName) {
return surface, true
}
}
return OperatorSurfaceContract{}, false
}
// OperatorSurfaceContractMatches reports whether a surface matches by stable id
// or display label.
func OperatorSurfaceContractMatches(surface OperatorSurfaceContract, surfaceID, surfaceName string) bool {
if surfaceID != "" && strings.EqualFold(strings.TrimSpace(surface.ID), surfaceID) {
return true
}
if surfaceName != "" && strings.EqualFold(strings.TrimSpace(surface.Label), surfaceName) {
return true
}
return false
}
// SurfaceAffordanceLabels formats enabled affordances with stable,
// operator-facing labels.
func SurfaceAffordanceLabels(affordances SurfaceAffordanceContract) []string {
labels := []string{}
if affordances.Tools {
labels = append(labels, "tools")
}
if affordances.Resources {
labels = append(labels, "resources")
}
if affordances.Prompts {
labels = append(labels, "prompts")
}
if affordances.CapabilityMetadata {
labels = append(labels, "capability metadata")
}
if affordances.InteractiveQuestions {
labels = append(labels, "interactive questions")
}
return labels
}
// ProjectPulseAssistantSurfaceToolContract projects the native Assistant
// provider-tool list into the shared surface summary. Registry-backed tools and
// Assistant-native interaction tools are separated deliberately: pulse_question
// is a native in-app affordance, not an MCP capability.
func ProjectPulseAssistantSurfaceToolContract(contract SurfaceContract, providerTools []ProviderTool) (SurfaceToolContract, bool) {
surface, ok := ResolveSurfaceAffordanceContract(contract, SurfaceIDPulseAssistant, "")
toolNames := ProviderToolNames(providerTools)
if !surface.Affordances.Tools {
toolNames = []string{}
}
nativeCatalog := NewProviderToolNameCatalog(AssistantNativeProviderToolNames())
registryNames := make([]string, 0, len(toolNames))
nativeNames := make([]string, 0, len(toolNames))
offeredNames := make([]string, 0, len(toolNames))
for _, name := range toolNames {
if nativeCatalog.Has(name) {
if !surface.Affordances.InteractiveQuestions {
continue
}
nativeNames = append(nativeNames, name)
offeredNames = append(offeredNames, name)
continue
}
registryNames = append(registryNames, name)
offeredNames = append(offeredNames, name)
}
return SurfaceToolContract{
SurfaceID: surface.SurfaceID,
SurfaceLabel: surface.SurfaceLabel,
ToolSource: SurfaceToolSourceAssistantRegistry,
ToolNames: offeredNames,
RegistryToolNames: registryNames,
NativeToolNames: nativeNames,
Affordances: surface.Affordances,
}.NormalizeCollections(), ok
}
// ProjectManifestSurfaceToolContract normalizes a manifest-published
// request/response tool allowlist into the shared surface summary for a
// manifest-declared external adapter. Streaming capabilities remain represented
// by resources, prompts, or notifications, not request/response tool entries.
func ProjectManifestSurfaceToolContract(manifest Manifest, surfaceID string) (SurfaceToolContract, bool) {
surfaceID = strings.TrimSpace(surfaceID)
if surfaceID == "" {
return SurfaceToolContract{}, false
}
surface, ok := FindOperatorSurfaceContract(manifest.SurfaceContract, surfaceID, "")
if !ok || !surface.ExternalAdapter {
return SurfaceToolContract{}, false
}
if contract, ok := FindManifestSurfaceToolContract(manifest, surface.ID); ok {
return normalizeManifestSurfaceToolContract(manifest, surface, contract), true
}
return SurfaceToolContract{}, false
}
func normalizeManifestSurfaceToolContract(manifest Manifest, surface OperatorSurfaceContract, contract SurfaceToolContract) SurfaceToolContract {
resolvedSurface, _ := ResolveSurfaceAffordanceContract(manifest.SurfaceContract, surface.ID, surface.Label)
contract = contract.NormalizeCollections()
if strings.TrimSpace(contract.SurfaceID) == "" {
contract.SurfaceID = resolvedSurface.SurfaceID
}
if strings.TrimSpace(contract.SurfaceLabel) == "" {
contract.SurfaceLabel = resolvedSurface.SurfaceLabel
}
if strings.TrimSpace(contract.ToolSource) == "" {
contract.ToolSource = SurfaceToolSourceCapabilityManifest
}
if SurfaceAffordancesDeclared(contract.Affordances) {
contract.Affordances = IntersectSurfaceAffordances(resolvedSurface.Affordances, contract.Affordances)
} else {
contract.Affordances = resolvedSurface.Affordances
}
if len(contract.ToolNames) == 0 && len(contract.CapabilityNames) > 0 {
contract.ToolNames = append([]string(nil), contract.CapabilityNames...)
}
if contract.Affordances.Tools {
toolNames := requestResponseCapabilityNamesForNames(manifest.Capabilities, contract.ToolNames)
contract.ToolNames = toolNames
contract.CapabilityNames = append([]string(nil), toolNames...)
} else {
contract.ToolNames = []string{}
contract.CapabilityNames = []string{}
}
contract.RegistryToolNames = nil
contract.NativeToolNames = nil
return contract.NormalizeCollections()
}
// IntersectSurfaceAffordances applies a surface-local narrowing contract without
// allowing that local contract to re-enable an affordance disabled by the
// operator surface.
func IntersectSurfaceAffordances(allowed SurfaceAffordanceContract, requested SurfaceAffordanceContract) SurfaceAffordanceContract {
return SurfaceAffordanceContract{
Tools: allowed.Tools && requested.Tools,
Resources: allowed.Resources && requested.Resources,
Prompts: allowed.Prompts && requested.Prompts,
CapabilityMetadata: allowed.CapabilityMetadata && requested.CapabilityMetadata,
InteractiveQuestions: allowed.InteractiveQuestions && requested.InteractiveQuestions,
}
}
// ResolveManifestSurfaceToolContract returns the normalized request/response
// surface tool contract for a manifest-backed external-agent surface. Missing
// published surface tool contracts fail closed rather than inferring tools from
// raw manifest capabilities.
func ResolveManifestSurfaceToolContract(manifest Manifest, surfaceID string) (SurfaceToolContract, bool) {
surfaceID = strings.TrimSpace(surfaceID)
if surfaceID == "" {
return SurfaceToolContract{}, false
}
return ProjectManifestSurfaceToolContract(manifest, surfaceID)
}
// ProjectManifestSurfaceToolContracts returns manifest-backed external-adapter
// surface tool summaries in manifest operator-surface order. Native Assistant
// tool availability is intentionally omitted because it is runtime-specific.
func ProjectManifestSurfaceToolContracts(manifest Manifest) []SurfaceToolContract {
contracts := make([]SurfaceToolContract, 0, len(manifest.SurfaceContract.OperatorSurfaces))
for _, surface := range manifest.SurfaceContract.OperatorSurfaces {
if !surface.ExternalAdapter || strings.TrimSpace(surface.ID) == "" {
continue
}
if contract, ok := ProjectManifestSurfaceToolContract(manifest, surface.ID); ok {
contracts = append(contracts, contract)
}
}
return contracts
}
// ProjectPulseIntelligenceSurfaceToolContracts returns the supported
// operator-facing surface tool summaries in manifest order. The function is
// intentionally a projection over the two canonical sources rather than a new
// tool registry.
func ProjectPulseIntelligenceSurfaceToolContracts(manifest Manifest, assistantTools []ProviderTool) []SurfaceToolContract {
contracts := make([]SurfaceToolContract, 0, len(manifest.SurfaceContract.OperatorSurfaces))
for _, surface := range manifest.SurfaceContract.OperatorSurfaces {
switch strings.TrimSpace(surface.ID) {
case SurfaceIDPulseAssistant:
contract, _ := ProjectPulseAssistantSurfaceToolContract(manifest.SurfaceContract, assistantTools)
contracts = append(contracts, contract)
default:
if surface.ExternalAdapter && strings.TrimSpace(surface.ID) != "" {
if contract, ok := ProjectManifestSurfaceToolContract(manifest, surface.ID); ok {
contracts = append(contracts, contract)
}
}
}
}
return contracts
}
func requestResponseCapabilityNamesForNames(capabilities []Capability, names []string) []string {
result := make([]string, 0, len(names))
seen := map[string]struct{}{}
for _, rawName := range names {
name := strings.TrimSpace(rawName)
if name == "" {
continue
}
if _, exists := seen[name]; exists {
continue
}
seen[name] = struct{}{}
if _, err := ResolveRequestResponseCapability(capabilities, name); err != nil {
continue
}
result = append(result, name)
}
return result
}