Files
dingtalk-workspace-cli/internal/cli/schema_runtime_registry.go
T

1000 lines
38 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package cli
import (
"encoding/json"
"fmt"
"sort"
"strings"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contractfinal"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/spf13/cobra"
)
type runtimeSchemaMetadataSources struct {
Agent agentMetadata
MCP embeddedMCPMetadata
}
var (
resolveEffectiveCommandRegistry = BuildEffectiveCommandRegistry
resolveBoundCommandRegistry = BindEffectiveCommandRegistry
resolveAssembleSchemaRegistry = AssembleSchemaRegistryFromBound
resolveValidateParameterDelivery = ValidateSchemaParameterBindingDelivery
assembleValidateBindings = ValidateSchemaParameterBindings
assembleCollectEntries = collectRuntimeSchemaEntriesFromBound
assembleRuntimeToolSpec = runtimeToolSpecFromMetadata
assembleTypedRegistry = SchemaRegistryFromRuntime
assembleMarshalRaw = marshalSchemaRaw
resolveReviewedDryRun = reviewedDryRunCapability
resolveRuntimeToolText = runtimeToolTextMetadataFromMetadata
resolveRuntimeParameters = runtimeCommandParameterSpecs
renderRegistrySnapshot = SchemaRegistry.ToSnapshotPayload
renderRegistryPayload = SchemaRegistry.ToPayload
renderRegistryProductSummary = ProductSpec.ToSummaryPayload
renderRegistryToolPayload = ToolSpec.ToPayload
renderRegistryToolSummary = ToolSpec.ToSummaryPayload
finalSchemaAgentMetadata = runtimeAgentMetadata
)
// ResolvedSchemaBuild is the single source-to-delivery hand-off used by the
// Catalog generator. Effective, Bound, and Registry are three views of one
// resolution pass: reviewed identity, executable Cobra binding, and the final
// typed Agent contract. Downstream gates and serializers must consume this
// value instead of rebuilding any view from the command tree.
//
// The command root is intentionally private. It lets delivery completeness
// inspect the same executable tree without allowing callers to construct a
// seemingly resolved build by assembling the exported fields themselves.
type ResolvedSchemaBuild struct {
effective EffectiveCommandRegistry
bound BoundCommandRegistry
registry SchemaRegistry
root *cobra.Command
}
// RegistryHash returns the semantic identity/navigation hash attached to this
// resolved build. It is an envelope value, not a second registry input.
func (resolved ResolvedSchemaBuild) RegistryHash() string {
return resolved.effective.SourceHash()
}
// CommandCount reports the reviewed effective command count for generator
// diagnostics without exposing a mutable registry view.
func (resolved ResolvedSchemaBuild) CommandCount() int {
return len(resolved.effective.Commands)
}
func pinnedRuntimeSchemaMetadataSources() runtimeSchemaMetadataSources {
return runtimeSchemaMetadataSources{
Agent: runtimeAgentMetadata(),
MCP: runtimeMCPMetadata(),
}
}
// ResolveSchemaBuild is the only assembly path from executable Cobra commands
// and reviewed metadata into the typed Agent contract. It resolves identity
// once, binds Cobra once, and assembles one SchemaRegistry from ContractFinal /
// contract.ProductDecl leaf declarations. Catalog gates and serialization consume the
// returned value directly; they never re-read overlays or merge sources.
func ResolveSchemaBuild(root *cobra.Command) (ResolvedSchemaBuild, error) {
if root == nil {
return ResolvedSchemaBuild{}, fmt.Errorf("resolve Schema build: root is nil")
}
effective, err := resolveEffectiveCommandRegistry(root)
if err != nil {
return ResolvedSchemaBuild{}, fmt.Errorf("build effective Schema command registry: %w", err)
}
bound, err := resolveBoundCommandRegistry(root, effective)
if err != nil {
return ResolvedSchemaBuild{}, fmt.Errorf("bind effective Schema command registry: %w", err)
}
registry, err := resolveAssembleSchemaRegistry(bound)
if err != nil {
return ResolvedSchemaBuild{}, err
}
return ResolvedSchemaBuild{
effective: effective,
bound: bound,
registry: registry,
root: root,
}, nil
}
// AssembleSchemaRegistry is retained for non-Catalog callers that only need
// the typed registry. Catalog production must use ResolveSchemaBuild so the
// bound/effective views remain attached to the exact same resolution pass.
func AssembleSchemaRegistry(root *cobra.Command) (SchemaRegistry, error) {
resolved, err := ResolveSchemaBuild(root)
if err != nil {
return SchemaRegistry{}, err
}
if err := resolveValidateParameterDelivery(resolved.bound, resolved.registry); err != nil {
return SchemaRegistry{}, fmt.Errorf("validate final Schema parameter binding delivery: %w", err)
}
return resolved.registry, nil
}
// AssembleSchemaRegistryFromBound resolves non-identity sources into the
// single typed ToolSpec model. Command discovery is intentionally impossible
// below this boundary: callers must first provide a fail-closed bound registry.
func AssembleSchemaRegistryFromBound(bound BoundCommandRegistry) (SchemaRegistry, error) {
if err := assembleValidateBindings(); err != nil {
return SchemaRegistry{}, fmt.Errorf("validate reviewed Schema parameter bindings: %w", err)
}
return assembleSchemaRegistryFromBound(bound, pinnedRuntimeSchemaMetadataSources())
}
// assembleSchemaOptions controls production vs test-isolated assembly.
// allowLegacyMetadata is never set on the production Catalog path: missing
// ContractFinal / ProductDecl must fail closed rather than reopen skill/MCP
// /agent-inject overlays.
type assembleSchemaOptions struct {
allowLegacyMetadata bool
}
func assembleSchemaRegistryFromBound(bound BoundCommandRegistry, metadata runtimeSchemaMetadataSources) (SchemaRegistry, error) {
return assembleSchemaRegistryFromBoundWithOptions(bound, metadata, assembleSchemaOptions{})
}
// assembleSchemaRegistryFromBoundAllowingLegacy is test-only isolation for the
// retired skill/MCP/agent-metadata overlay path. Production assembly must never
// call this entry.
func assembleSchemaRegistryFromBoundAllowingLegacy(bound BoundCommandRegistry, metadata runtimeSchemaMetadataSources) (SchemaRegistry, error) {
return assembleSchemaRegistryFromBoundWithOptions(bound, metadata, assembleSchemaOptions{allowLegacyMetadata: true})
}
func assembleSchemaRegistryFromBoundWithOptions(bound BoundCommandRegistry, metadata runtimeSchemaMetadataSources, opts assembleSchemaOptions) (SchemaRegistry, error) {
entries, err := assembleCollectEntries(bound)
if err != nil {
return SchemaRegistry{}, err
}
byProduct := make(map[string]*ProductSpec)
for _, entry := range entries {
var tool ToolSpec
if opts.allowLegacyMetadata {
tool, err = runtimeToolSpecAllowingLegacy(entry, metadata)
} else {
tool, err = assembleRuntimeToolSpec(entry, metadata)
}
if err != nil {
return SchemaRegistry{}, err
}
product := byProduct[entry.ProductID]
if product == nil {
selection, provenance, err := assembleProductSelection(entry, metadata, opts.allowLegacyMetadata)
if err != nil {
return SchemaRegistry{}, err
}
product = &ProductSpec{
ID: entry.ProductID,
Name: entry.ProductName,
Description: entry.ProductName,
Runtime: true,
Selection: selection,
FieldProvenance: provenance,
}
byProduct[entry.ProductID] = product
}
product.Tools = append(product.Tools, tool)
}
productIDs := make([]string, 0, len(byProduct))
for productID := range byProduct {
productIDs = append(productIDs, productID)
}
sort.Strings(productIDs)
products := make([]ProductSpec, 0, len(productIDs))
for _, productID := range productIDs {
products = append(products, *byProduct[productID])
}
registry, err := assembleTypedRegistry("runtime-command", products)
if err != nil {
return SchemaRegistry{}, fmt.Errorf("build typed Schema registry: %w", err)
}
registry.InterfaceMetadata, err = assembleMarshalRaw(interfaceMetadataSummaryFrom(metadata.MCP))
if err != nil {
return SchemaRegistry{}, fmt.Errorf("encode interface metadata summary: %w", err)
}
// Derive Agent metadata summary from the assembled ContractFinal /
// ProductDecl surface so runtime delivery and cmd_schema_catalog dumps
// share one Catalog blob (inject remains validation-only for the dump).
registry.AgentMetadata, err = assembleMarshalRaw(agentMetadataSummaryFromProducts(products))
if err != nil {
return SchemaRegistry{}, fmt.Errorf("encode Agent metadata summary: %w", err)
}
return registry, nil
}
func runtimeToolSpecFromMetadata(entry runtimeSchemaEntry, metadata runtimeSchemaMetadataSources) (ToolSpec, error) {
if final, ok := contractfinal.RuntimeContractFinal(entry.Command); ok {
return runtimeToolSpecFromContractFinal(entry, final, metadata)
}
canonicalPath := entry.ProductID + "." + entry.ToolName
return ToolSpec{}, fmt.Errorf("assemble Schema tool %s: missing RuntimeContractFinal (legacy skill/MCP/agent-metadata assembly is retired)", canonicalPath)
}
// assembleProductSelection requires ProductDecl on the production path.
// Legacy agent-product JSON overlay is test-isolation only.
func assembleProductSelection(entry runtimeSchemaEntry, metadata runtimeSchemaMetadataSources, allowLegacy bool) (contract.SelectionSpec, map[string]contract.FieldProvenance, error) {
if decl, ok := contract.LookupProductDecl(entry.ProductID); ok {
selection, provenance := contract.ProductSelectionFromDecl(decl)
return selection, provenance, nil
}
if allowLegacy {
selection, provenance, _ := agentProductContractForIDsFromMetadata(metadata.Agent, entry.ProductID, entry.SourceProductID)
return selection, provenance, nil
}
return contract.SelectionSpec{}, nil, fmt.Errorf("assemble Schema product %q: missing ProductDecl (legacy agent-metadata product selection is retired)", entry.ProductID)
}
// runtimeToolSpecAllowingLegacy is the test-isolated overlay path. Prefer
// ContractFinal when present; otherwise reopen retired skill/MCP/agent inject.
func runtimeToolSpecAllowingLegacy(entry runtimeSchemaEntry, metadata runtimeSchemaMetadataSources) (ToolSpec, error) {
if final, ok := contractfinal.RuntimeContractFinal(entry.Command); ok {
return runtimeToolSpecFromContractFinal(entry, final, metadata)
}
return runtimeToolSpecFromLegacyMetadata(entry, metadata)
}
// runtimeToolSpecFromContractFinal pass-throughs Contract-authored Schema fields.
// Declared values are the final data source; hints/registry text does not merge.
// Pinned MCP parameter metadata still participates in parameter resolution:
// interface_type and interface-side required are interface facts the CLI
// declaration does not own, and dropping them is a published-Schema
// compatibility break, not a declaration takeover.
func runtimeToolSpecFromContractFinal(entry runtimeSchemaEntry, final contract.ContractFinalPayload, metadata runtimeSchemaMetadataSources) (ToolSpec, error) {
canonicalPath := entry.ProductID + "." + entry.ToolName
constraints := runtimeCommandConstraints(entry.Command)
pinnedMeta, _ := pinnedMCPMetadataForEntryFrom(entry, metadata.Agent, metadata.MCP)
// Apply parameter declarations from the contract.ContractFinalPayload before the
// resolver reads them. The decls were put there by AttachContract at
// DeclareLeafMetadata time; now that all flags exist on the fully-built
// command tree, they can be emitted as dws.schema.* annotations.
if err := ApplyParamDecls(entry.Command, final.Parameters); err != nil {
return ToolSpec{}, fmt.Errorf("apply Contract Schema ParamDecls for %s: %w", canonicalPath, err)
}
parameters, err := resolveRuntimeParameters(entry.Command, canonicalPath, pinnedMeta.Parameters, constraints)
if err != nil {
return ToolSpec{}, fmt.Errorf("resolve Contract Schema parameters for %s: %w", canonicalPath, err)
}
identity := contract.ToolIdentitySpec{
ProductID: entry.ProductID,
SourceProductID: strings.TrimSpace(entry.SourceProductID),
Name: entry.ToolName,
CLIName: entry.CLIName,
CanonicalPath: canonicalPath,
Path: canonicalPath,
CLIPath: entry.CLIPath,
PrimaryCLIPath: entry.PrimaryCLIPath,
Group: entry.Group,
Aliases: append([]string(nil), entry.Aliases...),
IsAlias: false,
Source: entry.Source,
}
if final.Identity != nil {
if err := validateContractFinalIdentity(entry, *final.Identity, canonicalPath); err != nil {
return ToolSpec{}, err
}
id := *final.Identity
if id.ProductID != "" {
identity.ProductID = id.ProductID
}
if id.SourceProductID != "" {
identity.SourceProductID = id.SourceProductID
}
if id.Name != "" {
identity.Name = id.Name
}
if id.CLIName != "" {
identity.CLIName = id.CLIName
}
if id.CanonicalPath != "" {
identity.CanonicalPath = id.CanonicalPath
identity.Path = id.CanonicalPath
}
if id.CLIPath != "" {
identity.CLIPath = id.CLIPath
}
if id.PrimaryCLIPath != "" {
identity.PrimaryCLIPath = id.PrimaryCLIPath
}
if id.Group != "" {
identity.Group = id.Group
}
if len(id.Aliases) > 0 {
identity.Aliases = append([]string(nil), id.Aliases...)
}
if id.Source != "" {
identity.Source = id.Source
}
}
if identity.SourceProductID == identity.ProductID {
identity.SourceProductID = ""
}
// Text delivery: Cobra Long wins over declared Contract.Description when
// present (declared Description is mandatory and often a one-line restatement).
// Declared Title wins over Short. Provenance must name the real winner.
title, titleProv := contractFinalTextProvenance(
strings.TrimSpace(final.Title),
strings.TrimSpace(entry.Command.Short),
false, // prefer declared
)
description, descriptionProv := contractFinalTextProvenance(
strings.TrimSpace(final.Description),
strings.TrimSpace(entry.Command.Long),
true, // prefer cobra help
)
safety := contract.SafetySpec{}
if final.Safety != nil {
safety = *final.Safety
} else if risk, ok := RuntimeContractRisk(entry.Command); ok {
safety = applyContractRiskToSafety(safety, risk)
} else if gate, ok := RuntimeContractGate(entry.Command); ok {
safety = applyContractGateToSafety(safety, gate)
}
positionals := final.Positionals
if len(positionals) == 0 {
positionals = runtimeCommandPositionals(entry.Command)
}
var interfaceSpec contract.InterfaceSpec
if final.Interface != nil {
interfaceSpec = *final.Interface
}
var selection contract.SelectionSpec
if final.Selection != nil {
if final.Selection.Reviewed != nil {
return ToolSpec{}, fmt.Errorf("contract final selection for %s must not carry reviewed field: declaration is the final source", canonicalPath)
}
selection = *final.Selection
}
// Declared selection provenance metadata: the declaration lives in reviewed
// source code, so the catalog keeps the same uniform agent_* shape as
// legacy-path tools. Reviewed is assembly-derived (declarations are
// code-reviewed by construction), never author-provided.
if strings.TrimSpace(selection.AgentSummarySource) == "" && strings.TrimSpace(selection.AgentSummary) != "" {
selection.AgentSummarySource = "corecmd.ContractDecl"
}
if selection.SourceRefs == nil {
selection.SourceRefs = []string{"corecmd.ContractDecl"}
}
if strings.TrimSpace(selection.MetadataSource) == "" {
selection.MetadataSource = "corecmd.contract"
}
if selection.Reviewed == nil {
reviewed := true
selection.Reviewed = &reviewed
}
provenance := contractFinalProvenance(identity, title, description, titleProv, descriptionProv, safety, interfaceSpec, selection, final.DryRun)
return ToolSpecFromRuntime(RuntimeToolSpecInput{
Identity: identity,
Display: entry.ProductName,
Title: title,
Description: description,
MetadataSource: "corecmd.contract",
Parameters: parameters,
Constraints: constraints,
Positionals: positionals,
DryRun: final.DryRun,
Safety: safety,
Interface: interfaceSpec,
Selection: selection,
FieldProvenance: provenance,
})
}
// contractFinalTextProvenance picks delivered title/description text and the
// provenance that matches the real winner. preferCobra=true implements the
// Long-over-declared-Description rule; preferCobra=false keeps declared Title
// over Short.
func contractFinalTextProvenance(declared, cobra string, preferCobra bool) (string, contract.FieldProvenance) {
decl := strings.TrimSpace(declared)
help := strings.TrimSpace(cobra)
switch {
case preferCobra && help != "":
return help, resolvedFieldProvenance(
help, "cobra_help", "cobra_help", "cobra_help",
"cobra_help_preferred", "Cobra Long preferred over ContractDecl text",
)
case decl != "":
return decl, resolvedFieldProvenance(
decl, "corecmd.contract", "corecmd.ContractDecl", "contract_final",
"contract_pass_through", "Contract final Schema pass-through",
)
case help != "":
return help, resolvedFieldProvenance(
help, "cobra_help", "cobra_help", "cobra_help",
"cobra_help_fallback", "Cobra Long fallback when ContractDecl text is empty",
)
default:
// Keep a winner even when both sides are empty so the final-delivery
// provenance gate (winner value == delivered value) still holds.
return "", resolvedFieldProvenance(
"", "corecmd.contract", "corecmd.ContractDecl", "contract_final",
"contract_pass_through", "Contract final Schema pass-through",
)
}
}
// contractFinalProvenance records one pass-through winner per delivered field.
// The final Schema provenance gate requires a winner for every required tool
// field (safety/interface/agent_summary unconditionally, selection slices and
// dry_run when present), so declared leaves must emit the full set, not only
// the fields they happened to author. Title/description provenance must match
// the real text winner (cobra_help vs contract_final).
func contractFinalProvenance(identity contract.ToolIdentitySpec, title, description string, titleProv, descriptionProv contract.FieldProvenance, safety contract.SafetySpec, iface contract.InterfaceSpec, selection contract.SelectionSpec, dryRun *contract.DryRunSpec) map[string]contract.FieldProvenance {
prov := func(value any, sourceRef string) contract.FieldProvenance {
return resolvedFieldProvenance(
value,
"corecmd.contract",
sourceRef,
"contract_final",
"contract_pass_through",
"Contract final Schema pass-through",
)
}
out := map[string]contract.FieldProvenance{
"canonical_path": prov(identity.CanonicalPath, "corecmd.ContractDecl"),
"title": titleProv,
"description": descriptionProv,
"metadata_source": prov("corecmd.contract", "corecmd.ContractDecl"),
"effect": prov(safety.Effect, "corecmd.ContractDecl"),
"risk": prov(safety.Risk, "corecmd.ContractDecl"),
"confirmation": prov(safety.Confirmation, "corecmd.ContractDecl"),
"idempotency": prov(safety.Idempotency, "corecmd.ContractDecl"),
"interface_mode": prov(iface.Mode, "corecmd.ContractDecl"),
"availability": prov(iface.Availability, "corecmd.ContractDecl"),
"agent_summary": prov(selection.AgentSummary, "corecmd.ContractDecl"),
}
var ref any
if iface.Ref != nil {
ref = *iface.Ref
}
out["interface_ref"] = prov(ref, "corecmd.ContractDecl")
if strings.TrimSpace(iface.Reason) != "" ||
strings.TrimSpace(iface.Mode) == contract.InterfaceModeComposite ||
strings.TrimSpace(iface.Availability) == contract.InterfaceUnavailable {
out["interface_reason"] = prov(iface.Reason, "corecmd.ContractDecl")
}
for field, values := range map[string][]string{
"use_when": selection.UseWhen,
"avoid_when": selection.AvoidWhen,
"prerequisites": selection.Prerequisites,
"tips": selection.Tips,
"workflow_refs": selection.WorkflowRefs,
"examples": selection.Examples,
} {
if values != nil {
out[field] = prov(values, "corecmd.ContractDecl")
}
}
if selection.Reviewed != nil {
out["reviewed"] = prov(*selection.Reviewed, "corecmd.ContractDecl")
}
if dryRun != nil {
out["dry_run"] = prov(*dryRun, "corecmd.ContractDecl")
}
return out
}
// validateContractFinalIdentity guards the pass-through contract: on a bound
// (managed) leaf, a declared identity must agree with the bound tree entry.
// Otherwise the Schema catalog would publish an identity the registry never
// indexed. Registry-owned keys are compared exactly (including empty aliases);
// optional derived fields (cli_name/group/source) still fail only when
// non-empty and disagreeing.
func validateContractFinalIdentity(entry runtimeSchemaEntry, id contract.ToolIdentitySpec, canonicalPath string) error {
mismatches := make([]string, 0, 10)
checkExact := func(field, declared, bound string) {
declared = strings.TrimSpace(declared)
bound = strings.TrimSpace(bound)
if declared != bound {
mismatches = append(mismatches, fmt.Sprintf("%s: declared %q, bound %q", field, declared, bound))
}
}
checkOptional := func(field, declared, bound string) {
declared = strings.TrimSpace(declared)
if declared != "" && declared != strings.TrimSpace(bound) {
mismatches = append(mismatches, fmt.Sprintf("%s: declared %q, bound %q", field, declared, bound))
}
}
checkExact("product_id", id.ProductID, entry.ProductID)
checkExact("name", id.Name, entry.ToolName)
checkExact("canonical_path", id.CanonicalPath, canonicalPath)
cliPath := strings.TrimSpace(id.CLIPath)
primary := strings.TrimSpace(id.PrimaryCLIPath)
if cliPath == "" {
cliPath = primary
}
if primary == "" {
primary = cliPath
}
checkExact("cli_path", cliPath, entry.CLIPath)
checkExact("primary_cli_path", primary, entry.PrimaryCLIPath)
// Empty / product-equal source_product_id are equivalent (registry decode
// defaults omitted source to product_id; Contract may omit it).
checkExact(
"source_product_id",
normalizeIdentitySourceProduct(id.SourceProductID, entry.ProductID),
normalizeIdentitySourceProduct(entry.SourceProductID, entry.ProductID),
)
checkOptional("cli_name", id.CLIName, entry.CLIName)
checkOptional("group", id.Group, entry.Group)
checkOptional("source", id.Source, entry.Source)
if !stringSetsEqual(id.Aliases, entry.Aliases) {
mismatches = append(mismatches, fmt.Sprintf("aliases: declared %v, bound %v", id.Aliases, entry.Aliases))
}
if len(mismatches) > 0 {
sort.Strings(mismatches)
return fmt.Errorf("contract final identity mismatch for %s: %s", canonicalPath, strings.Join(mismatches, "; "))
}
return nil
}
func stringSetsEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
counts := make(map[string]int, len(a))
for _, v := range a {
counts[strings.TrimSpace(v)]++
}
for _, v := range b {
k := strings.TrimSpace(v)
counts[k]--
if counts[k] < 0 {
return false
}
}
return true
}
// runtimeToolSpecFromLegacyMetadata rebuilds a ToolSpec from skill/MCP/agent
// inject overlays. Production assembly never calls this; only
// assembleSchemaRegistryFromBoundAllowingLegacy (tests) may.
func runtimeToolSpecFromLegacyMetadata(entry runtimeSchemaEntry, metadata runtimeSchemaMetadataSources) (ToolSpec, error) {
canonicalPath := entry.ProductID + "." + entry.ToolName
dryRun, err := resolveReviewedDryRun(canonicalPath)
if err != nil {
return ToolSpec{}, fmt.Errorf("resolve reviewed dry-run capability for %s: %w", canonicalPath, err)
}
pinnedMeta, hasPinnedMeta := pinnedMCPMetadataForEntryFrom(entry, metadata.Agent, metadata.MCP)
title, description, metadataSource, textProvenance, err := resolveRuntimeToolText(entry, metadata)
if err != nil {
return ToolSpec{}, fmt.Errorf("resolve Schema text metadata for %s: %w", canonicalPath, err)
}
constraints := runtimeCommandConstraints(entry.Command)
parameters, err := resolveRuntimeParameters(entry.Command, canonicalPath, pinnedMeta.Parameters, constraints)
if err != nil {
return ToolSpec{}, fmt.Errorf("resolve Schema parameters for %s: %w", canonicalPath, err)
}
paths := []string{entry.PrimaryCLIPath, entry.CLIPath, canonicalPath}
paths = append(paths, entry.Aliases...)
safety, interfaceSpec, selection, provenance, _ := agentToolContractForPathsFromMetadata(metadata.Agent, paths...)
if contractRisk, ok := RuntimeContractRisk(entry.Command); ok {
safety = applyContractRiskToSafety(safety, contractRisk)
} else if gate, ok := RuntimeContractGate(entry.Command); ok {
safety = applyContractGateToSafety(safety, gate)
}
if metadataSource == "" && hasPinnedMeta {
metadataSource = ProvenanceEmbeddedMCPMetadata
textProvenance["metadata_source"] = runtimeSchemaFieldProvenance(
runtimeSchemaStringCandidate(metadataSource, "metadata_source_resolution"),
)
}
if provenance == nil {
provenance = map[string]contract.FieldProvenance{}
}
for field, fieldProvenance := range textProvenance {
provenance[field] = fieldProvenance
}
if contractRisk, ok := RuntimeContractRisk(entry.Command); ok {
provenance["risk"] = resolvedFieldProvenance(
safety.Risk,
"corecmd.contract",
"dws.schema.risk",
"reviewed_explicit",
"contract_risk_annotation",
"Contract Risk embedded on Cobra leaf ("+contractRisk+")",
)
provenance["confirmation"] = resolvedFieldProvenance(
safety.Confirmation,
"corecmd.contract",
"dws.schema.risk",
"reviewed_explicit",
"contract_risk_annotation",
"Contract Risk embedded on Cobra leaf ("+contractRisk+")",
)
provenance["effect"] = resolvedFieldProvenance(
safety.Effect,
"corecmd.contract",
"dws.schema.risk",
"reviewed_explicit",
"contract_risk_annotation",
"Contract Risk embedded on Cobra leaf ("+contractRisk+")",
)
} else if gate, ok := RuntimeContractGate(entry.Command); ok {
provenance["runtime_gate"] = resolvedFieldProvenance(
gate,
"corecmd.contract",
"dws.schema.runtime_gate",
"reviewed_explicit",
"contract_runtime_gate_annotation",
"Confirmation path annotated on Cobra leaf ("+gate+")",
)
provenance["confirmation"] = resolvedFieldProvenance(
safety.Confirmation,
"corecmd.contract",
"dws.schema.runtime_gate",
"reviewed_explicit",
"contract_runtime_gate_annotation",
"Confirmation path annotated on Cobra leaf ("+gate+")",
)
}
provenance["canonical_path"] = entry.IdentityField
if dryRun != nil {
provenance["dry_run"] = resolvedFieldProvenance(
*dryRun,
"reviewed_dry_run_registry",
"internal/cli/schema_dry_run_capabilities.go",
"reviewed_explicit",
"exact_canonical_lookup",
"reviewed positive dry-run capability",
)
}
sourceProductID := strings.TrimSpace(entry.SourceProductID)
if sourceProductID == entry.ProductID {
sourceProductID = ""
}
return ToolSpecFromRuntime(RuntimeToolSpecInput{
Identity: contract.ToolIdentitySpec{
ProductID: entry.ProductID,
SourceProductID: sourceProductID,
Name: entry.ToolName,
CLIName: entry.CLIName,
CanonicalPath: canonicalPath,
Path: canonicalPath,
CLIPath: entry.CLIPath,
PrimaryCLIPath: entry.PrimaryCLIPath,
Group: entry.Group,
Aliases: append([]string(nil), entry.Aliases...),
IsAlias: false,
Source: entry.Source,
},
Display: entry.ProductName,
Title: title,
Description: description,
MetadataSource: metadataSource,
Parameters: parameters,
Constraints: constraints,
Positionals: runtimeCommandPositionals(entry.Command),
DryRun: dryRun,
Safety: safety,
Interface: interfaceSpec,
Selection: selection,
FieldProvenance: provenance,
})
}
func marshalSchemaRaw(value any) (json.RawMessage, error) {
data, err := json.Marshal(value)
if err != nil {
return nil, err
}
return json.RawMessage(data), nil
}
func runtimeSchemaPayloadFromRegistry(registry SchemaRegistry, args []string) (map[string]any, error) {
index, err := registry.Index()
if err != nil {
return nil, err
}
registry = index.Registry()
if len(args) == 0 {
snapshot, err := renderRegistrySnapshot(registry)
if err != nil {
return nil, err
}
return snapshot.Catalog, nil
}
raw := strings.TrimSpace(args[0])
if tool, ok := index.ResolveQuery(raw); ok {
tool = schemaToolForResolvedPath(tool, raw)
return renderRegistryToolPayload(tool)
}
tokens := splitSchemaPathTokens(raw)
if len(tokens) == 1 {
if product, ok := index.Product(tokens[0]); ok {
payload, err := renderRegistryProductSummary(product)
if err != nil {
return nil, err
}
return map[string]any{
"kind": "schema",
"level": "product",
"count": len(product.Tools),
"product": payload,
"source": registry.Source,
}, nil
}
}
if len(tokens) > 1 {
path := strings.Join(tokens, " ")
if product, ok := index.Product(tokens[0]); ok {
tools := make([]map[string]any, 0)
for _, tool := range product.Tools {
if !schemaToolUnderGroup(tool, path) {
continue
}
summary, summaryErr := renderRegistryToolSummary(tool)
if summaryErr != nil {
return nil, summaryErr
}
tools = append(tools, summary)
}
if len(tools) > 0 {
return map[string]any{
"kind": "schema",
"level": "group",
"path": path,
"count": len(tools),
"tools": tools,
"source": registry.Source,
}, nil
}
}
}
return nil, apperrors.NewValidation("unknown runtime schema path " + strconvQuote(raw))
}
func runtimeSchemaAllPayloadFromRegistry(registry SchemaRegistry) (map[string]any, error) {
return renderRegistryPayload(registry)
}
func schemaToolForResolvedPath(tool ToolSpec, raw string) ToolSpec {
normalized := normalizeSchemaQueryCLIPath(raw)
if normalized == "" || normalized == tool.Identity.CLIPath || normalized == tool.Identity.PrimaryCLIPath {
return tool
}
for _, alias := range tool.Identity.Aliases {
if normalizeSchemaCLIPath(alias) == normalized {
tool.Identity.CLIPath = normalizeSchemaCLIPath(alias)
tool.Identity.IsAlias = true
return tool
}
}
return tool
}
func schemaToolUnderGroup(tool ToolSpec, group string) bool {
prefix := normalizeSchemaCLIPath(group) + " "
paths := append([]string{tool.Identity.CLIPath, tool.Identity.PrimaryCLIPath}, tool.Identity.Aliases...)
for _, path := range paths {
if strings.HasPrefix(normalizeSchemaCLIPath(path), prefix) {
return true
}
}
return false
}
// validateSchemaRegistryAgainstCommandRegistry compares identities as exact
// sets, not counts. A stale/missing primary path or alias must fail generation
// even when another executable path happens to preserve the canonical count.
func validateSchemaRegistryAgainstCommandRegistry(registry SchemaRegistry, commandRegistry EffectiveCommandRegistry) error {
index, err := registry.Index()
if err != nil {
return err
}
publicCommands := make(map[string]CommandSpec)
for canonical, command := range commandRegistry.ByCanonical {
if command.Visibility == SchemaVisibilityPublic {
publicCommands[canonical] = command
}
}
if got, want := len(index.CanonicalPaths()), len(publicCommands); got != want {
return fmt.Errorf("typed Schema registry contains %d canonical tools, reviewed CommandRegistry contains %d", got, want)
}
for canonical, expected := range publicCommands {
tool, ok := index.Resolve(canonical)
if !ok {
return fmt.Errorf("reviewed CommandRegistry canonical %s is missing from typed Schema registry", canonical)
}
if actual := normalizeSchemaCLIPath(tool.Identity.PrimaryCLIPath); actual != expected.PrimaryCLIPath {
return fmt.Errorf("schema tool %s primary path %q disagrees with reviewed CommandRegistry %q", canonical, actual, expected.PrimaryCLIPath)
}
actualSourceProduct := strings.TrimSpace(tool.Identity.SourceProductID)
if actualSourceProduct == "" {
actualSourceProduct = tool.Identity.ProductID
}
expectedSourceProduct := strings.TrimSpace(expected.SourceProductID)
if expectedSourceProduct == "" {
expectedSourceProduct = tool.Identity.ProductID
}
if actualSourceProduct != expectedSourceProduct {
return fmt.Errorf("schema tool %s source product %q disagrees with reviewed CommandRegistry %q", canonical, actualSourceProduct, expectedSourceProduct)
}
if actualSource := strings.TrimSpace(tool.Identity.Source); actualSource != strings.TrimSpace(expected.Source) {
return fmt.Errorf("schema tool %s identity source %q disagrees with effective CommandRegistry %q", canonical, actualSource, expected.Source)
}
actualAliases := sortedUniqueStrings(tool.Identity.Aliases)
expectedAliases := sortedUniqueStrings(expected.Aliases)
if strings.Join(actualAliases, "\x00") != strings.Join(expectedAliases, "\x00") {
return fmt.Errorf("schema tool %s aliases %q disagree with reviewed CommandRegistry %q", canonical, actualAliases, expectedAliases)
}
}
return nil
}
// validateSchemaRegistryAgentMetadata compares exact canonical sets after
// resolving generated metadata keys through the same SchemaIndex. Counts alone
// cannot detect one missing tool being masked by one duplicate alias.
//
// When Agent metadata is not injected (shipped runtime / live assembly without
// the Catalog generator), the retired embed is empty. In that mode validate that
// every assembled tool already carries selection prose from ContractFinal
// instead of reopening schema_agent_metadata/.
func validateSchemaRegistryAgentMetadata(registry SchemaRegistry) error {
index, err := registry.Index()
if err != nil {
return err
}
metadata := finalSchemaAgentMetadata()
if len(metadata.Tools) == 0 {
var problems []string
for _, canonical := range index.CanonicalPaths() {
// CanonicalPaths is derived from the same index Resolve reads, so a
// miss is impossible for a consistent SchemaIndex.
tool, _ := index.Resolve(canonical)
if strings.TrimSpace(tool.Selection.AgentSummary) == "" {
problems = append(problems, fmt.Sprintf("final Schema tool %s has no agent_summary without injected Agent metadata", canonical))
}
}
if len(problems) > 0 {
sort.Strings(problems)
return fmt.Errorf("%s", strings.Join(problems, "; "))
}
return nil
}
resolved := make(map[string]string, len(metadata.Tools))
var problems []string
keys := make([]string, 0, len(metadata.Tools))
for key := range metadata.Tools {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
tool, ok := index.Resolve(key)
if !ok {
problems = append(problems, fmt.Sprintf("Agent metadata key %q does not resolve in final Schema registry", key))
continue
}
canonical := tool.Identity.CanonicalPath
if previous := resolved[canonical]; previous != "" && previous != key {
problems = append(problems, fmt.Sprintf("Agent metadata keys %q and %q both resolve to %s", previous, key, canonical))
continue
}
resolved[canonical] = key
}
for _, canonical := range index.CanonicalPaths() {
if resolved[canonical] == "" {
problems = append(problems, fmt.Sprintf("final Schema tool %s has no generated Agent metadata", canonical))
}
}
if len(problems) > 0 {
sort.Strings(problems)
return fmt.Errorf("%s", strings.Join(problems, "; "))
}
return nil
}
// validateFinalSchemaProvenanceCoverage ensures every field resolved from
// competing contract sources carries a mechanically checkable winner. The
// per-field equality invariant itself is enforced by ToolSpec.Validate.
func validateFinalSchemaProvenanceCoverage(registry SchemaRegistry) error {
var problems []string
require := func(owner, field string, provenance map[string]contract.FieldProvenance) {
if _, ok := provenance[field]; !ok {
problems = append(problems, fmt.Sprintf("Schema %s has no provenance for %s", owner, field))
}
}
for _, product := range registry.Products {
if strings.TrimSpace(product.Selection.AgentSummary) != "" {
require("product "+product.ID, "agent_summary", product.FieldProvenance)
}
if product.Selection.UseWhen != nil {
require("product "+product.ID, "use_when", product.FieldProvenance)
}
if product.Selection.AvoidWhen != nil {
require("product "+product.ID, "avoid_when", product.FieldProvenance)
}
for _, tool := range product.Tools {
canonical := tool.Identity.CanonicalPath
if err := tool.Validate(); err != nil {
problems = append(problems, err.Error())
continue
}
for _, field := range requiredToolProvenanceFields {
require("tool "+canonical, field, tool.FieldProvenance)
}
if tool.DryRun != nil {
require("tool "+canonical, "dry_run", tool.FieldProvenance)
}
// interface_reason is part of the final interface contract only when
// the disposition requires or actually delivers a reason. An MCP or
// local available command with no reason has no resolver winner to
// invent; unavailable/composite commands fail closed without one.
if strings.TrimSpace(tool.Interface.Reason) != "" ||
strings.TrimSpace(tool.Interface.Mode) == contract.InterfaceModeComposite ||
strings.TrimSpace(tool.Interface.Availability) == contract.InterfaceUnavailable {
require("tool "+canonical, "interface_reason", tool.FieldProvenance)
}
selectionValues := map[string][]string{
"use_when": tool.Selection.UseWhen,
"avoid_when": tool.Selection.AvoidWhen,
"prerequisites": tool.Selection.Prerequisites,
"tips": tool.Selection.Tips,
"workflow_refs": tool.Selection.WorkflowRefs,
"examples": tool.Selection.Examples,
}
for _, field := range conditionalSelectionProvenanceFields {
// A non-nil empty slice is an authored [] winner, not absence.
if selectionValues[field] != nil {
require("tool "+canonical, field, tool.FieldProvenance)
}
}
if tool.Selection.Reviewed != nil {
require("tool "+canonical, "reviewed", tool.FieldProvenance)
}
for field, value := range map[string]string{
"title": tool.Title,
"description": tool.Description,
"metadata_source": tool.MetadataSource,
} {
if strings.TrimSpace(value) != "" {
require("tool "+canonical, field, tool.FieldProvenance)
}
}
for _, parameter := range tool.Parameters {
owner := "tool " + canonical + " parameter " + parameter.Name
for _, field := range requiredParameterProvenanceFields {
require(owner, field, parameter.FieldProvenance)
}
if parameter.CLIRequired {
require(owner, "cli_required", parameter.FieldProvenance)
}
if parameter.InterfaceType != "" {
require(owner, "interface_type", parameter.FieldProvenance)
}
if parameter.Format != "" {
require(owner, "format", parameter.FieldProvenance)
}
if len(parameter.Example) > 0 {
require(owner, "example", parameter.FieldProvenance)
}
if len(parameter.Enum) > 0 {
require(owner, "enum", parameter.FieldProvenance)
}
}
}
}
if len(problems) > 0 {
sort.Strings(problems)
return fmt.Errorf("%s", strings.Join(problems, "; "))
}
return nil
}