Files
chichuan 55d94d3b58 perf: skip redundant JSON validation when projecting typed Schema values
typedJSONValue marshaled a typed value and then routed the result through
rawJSONValue, which runs json.Valid before decoding. On that path the input is
whatever json.Marshal has just produced, so the validation scan can only ever
succeed: it re-read every marshaled document for nothing.

The decode step is now shared by both entry points. rawJSONValue keeps its
json.Valid check, because it still accepts untrusted input, while typedJSONValue
decodes what it marshaled directly. Across the 1121-tool set this removes about a
third of the Schema Catalog projection work: the internal/app schema suite goes
from 26.0s to 17.2s uninstrumented, and from 291.1s to 241.0s under -race.

The delivered Catalog is byte-for-byte unchanged. check-generated-drift,
check-schema-catalog and check-schema-binary each regenerate the same
source_hash sha256:93b8d44eb163bd2898c78397d22af92d378e3dc4e20f56b33277b51e4342e2e6,
and the two error contracts are preserved: typedJSONValue still rejects a value
json.Marshal cannot encode, and rawJSONValue still rejects invalid JSON.
2026-08-16 22:21:16 +08:00

1164 lines
39 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cli
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
)
// SchemaRegistry is the resolved, typed Agent contract. Source adapters and
// precedence resolvers populate this model; wire payloads are rendered only at
// the outer boundary by ToPayload. This keeps map[string]any out of the
// contract assembly path.
type SchemaRegistry struct {
Kind string
Level string
Source string
Products []ProductSpec
AgentMetadata json.RawMessage
}
// ProductSpec is one deterministic product grouping in SchemaRegistry.
type ProductSpec struct {
ID string
Name string
Description string
Runtime bool
Tools []ToolSpec
Selection contract.SelectionSpec
FieldProvenance map[string]contract.FieldProvenance
}
// ToolSpec is the single assembled representation of a command contract.
// Safety, interface and selection are typed sub-models even though ToPayload
// preserves the historical flat JSON shape for compatibility.
type ToolSpec struct {
Identity contract.ToolIdentitySpec
Display string
Title string
Description string
MetadataSource string
Parameters []ParameterSpec
Constraints RuntimeSchemaConstraints
Positionals []contract.RuntimeSchemaPositional
DryRun *contract.DryRunSpec
Result *contract.ResultSpec
Pagination *contract.PaginationSpec
Safety contract.SafetySpec
Interface contract.InterfaceSpec
Selection contract.SelectionSpec
FieldProvenance map[string]contract.FieldProvenance
}
// ParameterSpec is one resolved CLI flag projection. Name is the CLI flag key
// used in the legacy parameters object. Default and Example are RawMessage
// because JSON literals are polymorphic; they are still typed JSON values and
// never participate in precedence as map[string]any.
type ParameterSpec struct {
Name string
Type string
Description string
Property string
Required bool
CLIRequired bool
RequiredWhen string
Default json.RawMessage
InterfaceDefault json.RawMessage
Example json.RawMessage
Format string
Enum []string
InterfaceDescription string
InterfaceType string
FieldProvenance map[string]contract.FieldProvenance
}
// Final provenance coverage is an explicit contract, not a truthiness check.
// These fields all pass through a precedence resolver and therefore must keep
// a winner even when the delivered value is false, "", [], or null.
var requiredToolProvenanceFields = [...]string{
"canonical_path",
"effect",
"risk",
"confirmation",
"idempotency",
"interface_ref",
"interface_mode",
"availability",
"agent_summary",
}
var conditionalSelectionProvenanceFields = [...]string{
"use_when",
"avoid_when",
"prerequisites",
"tips",
"workflow_refs",
"examples",
}
var requiredParameterProvenanceFields = [...]string{
"property",
"type",
"description",
"required",
"required_when",
}
// RuntimeToolSpecInput is the typed hand-off from runtime source resolution to
// contract assembly. Adapters resolve candidates first, then call
// ToolSpecFromRuntime exactly once; payload rendering does no further merging.
type RuntimeToolSpecInput struct {
Identity contract.ToolIdentitySpec
Display string
Title string
Description string
MetadataSource string
Parameters []ParameterSpec
Constraints RuntimeSchemaConstraints
Positionals []contract.RuntimeSchemaPositional
DryRun *contract.DryRunSpec
Result *contract.ResultSpec
Pagination *contract.PaginationSpec
Safety contract.SafetySpec
Interface contract.InterfaceSpec
Selection contract.SelectionSpec
FieldProvenance map[string]contract.FieldProvenance
}
// SchemaIndex is the immutable navigation view over one normalized registry.
// It mirrors Lark's typed catalog split: SchemaRegistry owns data, SchemaIndex
// owns deterministic identity/path resolution, and serializers only render a
// resolved ToolSpec.
type SchemaIndex struct {
registry SchemaRegistry
byProduct map[string]int
byCanonical map[string]schemaToolLocation
byToolPath map[string]string
byCLIPath map[string]string
}
type schemaToolLocation struct {
product int
tool int
}
// SchemaSnapshotPayload is the final wire boundary for the release snapshot.
// Catalog summaries and full Tools are both derived from the same typed
// ToolSpec instances, preventing the two snapshot layers from drifting.
type SchemaSnapshotPayload struct {
Catalog map[string]any
Tools map[string]map[string]any
}
var (
snapshotToolSummary = ToolSpec.ToSummaryPayload
snapshotToolPayload = ToolSpec.ToPayload
snapshotProductSummary = ProductSpec.ToSummaryPayload
)
// SchemaRegistryFromRuntime constructs and validates the one registry shared
// by leaf lookup, Catalog summaries and full export.
func SchemaRegistryFromRuntime(source string, products []ProductSpec) (SchemaRegistry, error) {
registry := SchemaRegistry{
Kind: "schema",
Level: "catalog",
Source: strings.TrimSpace(source),
Products: products,
}.Sorted()
if _, err := registry.Index(); err != nil {
return SchemaRegistry{}, err
}
return registry, nil
}
// ToolSpecFromRuntime validates and normalizes the fully resolved runtime
// input. It does not choose between sources; that is the resolver's job.
// Snapshot loading shares this path and validates a delivered winner exactly
// as serialized: it deliberately does not repair provenance, so a snapshot
// whose winner differs from the final field must fail closed.
func ToolSpecFromRuntime(input RuntimeToolSpecInput) (ToolSpec, error) {
spec := ToolSpec(input).normalized()
if err := spec.Validate(); err != nil {
return ToolSpec{}, err
}
return spec, nil
}
func (t ToolSpec) provenanceValue(field string) (any, bool) {
switch field {
case "canonical_path":
return t.Identity.CanonicalPath, true
case "title":
return t.Title, true
case "description":
return t.Description, true
case "metadata_source":
return t.MetadataSource, true
case "dry_run":
return t.DryRun, true
case "effect":
return t.Safety.Effect, true
case "effect_source":
return t.Safety.EffectSource, true
case "risk":
return t.Safety.Risk, true
case "confirmation":
return t.Safety.Confirmation, true
case "idempotency":
return t.Safety.Idempotency, true
case "interface_ref":
return t.Interface.Ref, true
case "interface_mode":
return t.Interface.Mode, true
case "availability":
return t.Interface.Availability, true
case "interface_reason":
return t.Interface.Reason, true
case "agent_summary":
return t.Selection.AgentSummary, true
case "use_when":
return t.Selection.UseWhen, true
case "avoid_when":
return t.Selection.AvoidWhen, true
case "prerequisites":
return t.Selection.Prerequisites, true
case "tips":
return t.Selection.Tips, true
case "workflow_refs":
return t.Selection.WorkflowRefs, true
case "examples":
return t.Selection.Examples, true
case "reviewed":
return t.Selection.Reviewed, true
default:
return nil, false
}
}
func (p ParameterSpec) provenanceValue(field string) (any, bool) {
switch field {
case "name":
return p.Name, true
case "type":
return p.Type, true
case "description":
return p.Description, true
case "property":
return p.Property, true
case "required":
return p.Required, true
case "cli_required":
return p.CLIRequired, true
case "required_when":
return p.RequiredWhen, true
case "default":
return p.Default, true
case "interface_default":
return p.InterfaceDefault, true
case "example":
return p.Example, true
case "format":
return p.Format, true
case "enum":
return p.Enum, true
case "interface_description":
return p.InterfaceDescription, true
case "interface_type":
return p.InterfaceType, true
default:
return nil, false
}
}
// Index validates all registry identities and returns deterministic navigation
// indexes. Canonical paths, primary CLI paths and reviewed aliases must each
// resolve unambiguously.
func (r SchemaRegistry) Index() (SchemaIndex, error) {
r = r.Sorted()
index := SchemaIndex{
registry: r,
byProduct: make(map[string]int, len(r.Products)),
byCanonical: make(map[string]schemaToolLocation),
byToolPath: make(map[string]string),
byCLIPath: make(map[string]string),
}
for productIndex, product := range r.Products {
if product.ID == "" {
return SchemaIndex{}, fmt.Errorf("schema product id is empty")
}
if _, exists := index.byProduct[product.ID]; exists {
return SchemaIndex{}, fmt.Errorf("duplicate schema product %q", product.ID)
}
index.byProduct[product.ID] = productIndex
for field, provenance := range product.FieldProvenance {
if value, ok := product.provenanceValue(field); ok {
if err := validateFinalFieldProvenance("product "+product.ID, field, provenance, value); err != nil {
return SchemaIndex{}, err
}
}
}
for toolIndex, tool := range product.Tools {
if err := tool.Validate(); err != nil {
return SchemaIndex{}, err
}
if err := validateCanonicalToolIdentity(tool); err != nil {
return SchemaIndex{}, err
}
if tool.Identity.ProductID != product.ID {
return SchemaIndex{}, fmt.Errorf("tool %s belongs to product %q, not containing product %q", tool.Identity.CanonicalPath, tool.Identity.ProductID, product.ID)
}
canonical := tool.Identity.CanonicalPath
if _, exists := index.byCanonical[canonical]; exists {
return SchemaIndex{}, fmt.Errorf("duplicate schema tool %q", canonical)
}
if existing, exists := index.byToolPath[canonical]; exists && existing != canonical {
return SchemaIndex{}, fmt.Errorf("canonical path %q conflicts with contract path for %s", canonical, existing)
}
index.byCanonical[canonical] = schemaToolLocation{product: productIndex, tool: toolIndex}
toolPaths := []string{tool.Identity.Path}
if tool.Identity.SourceProductID != "" && tool.Identity.SourceProductID != tool.Identity.ProductID {
toolPaths = append(toolPaths, tool.Identity.SourceProductID+"."+tool.Identity.Name)
}
for _, path := range sortedUniqueStrings(toolPaths) {
if path == canonical {
continue
}
if existing, exists := index.byToolPath[path]; exists && existing != canonical {
return SchemaIndex{}, fmt.Errorf("schema contract path %q resolves to both %s and %s", path, existing, canonical)
}
if _, exists := index.byCanonical[path]; exists {
return SchemaIndex{}, fmt.Errorf("schema contract path %q conflicts with a canonical path", path)
}
index.byToolPath[path] = canonical
}
paths := append([]string{tool.Identity.CLIPath, tool.Identity.PrimaryCLIPath}, tool.Identity.Aliases...)
for _, path := range sortedUniqueStrings(paths) {
path = normalizeSchemaCLIPath(path)
if existing, exists := index.byCLIPath[path]; exists && existing != canonical {
return SchemaIndex{}, fmt.Errorf("schema CLI path %q resolves to both %s and %s", path, existing, canonical)
}
index.byCLIPath[path] = canonical
}
}
}
return index, nil
}
// validateCanonicalToolIdentity applies only to ToolSpecs stored in the
// registry. Alias lookup creates a detached query view later, where cli_path
// and is_alias intentionally change; that projection is never allowed back
// into SchemaRegistry, --all, or the delivered Catalog.
func validateCanonicalToolIdentity(tool ToolSpec) error {
id := tool.Identity
canonical := strings.TrimSpace(id.CanonicalPath)
cliPath := normalizeSchemaCLIPath(id.CLIPath)
primaryCLIPath := normalizeSchemaCLIPath(id.PrimaryCLIPath)
if cliPath != primaryCLIPath {
return fmt.Errorf("canonical Schema tool %s cli_path %q must equal primary_cli_path %q", canonical, cliPath, primaryCLIPath)
}
if id.IsAlias {
return fmt.Errorf("canonical Schema tool %s must have is_alias=false", canonical)
}
return nil
}
// Registry returns the detached normalized registry owned by this index.
func (i SchemaIndex) Registry() SchemaRegistry { return i.registry }
// Product resolves one product by exact stable ID.
func (i SchemaIndex) Product(id string) (ProductSpec, bool) {
position, ok := i.byProduct[strings.TrimSpace(id)]
if !ok {
return ProductSpec{}, false
}
return i.registry.Products[position], true
}
// Resolve resolves either an exact canonical path or a normalized CLI path.
// No inference or fuzzy matching occurs in this layer.
func (i SchemaIndex) Resolve(path string) (ToolSpec, bool) {
path = strings.TrimSpace(path)
canonical := path
location, ok := i.byCanonical[canonical]
if !ok {
canonical, ok = i.byToolPath[path]
}
if !ok {
canonical, ok = i.byCLIPath[normalizeSchemaCLIPath(path)]
if !ok {
return ToolSpec{}, false
}
}
location = i.byCanonical[canonical]
return i.registry.Products[location.product].Tools[location.tool], true
}
// ResolveQuery adds compatibility for dotted and slash-separated CLI paths at
// the user-facing query boundary. Resolve remains strict because Registry
// validation uses it to detect missing canonical identities without falling
// through to a similarly spelled CLI path.
func (i SchemaIndex) ResolveQuery(path string) (ToolSpec, bool) {
if tool, ok := i.Resolve(path); ok {
return tool, true
}
canonical, ok := i.byCLIPath[normalizeSchemaQueryCLIPath(path)]
if !ok {
return ToolSpec{}, false
}
location := i.byCanonical[canonical]
return i.registry.Products[location.product].Tools[location.tool], true
}
// CanonicalPaths returns the complete tool identity set in stable order.
func (i SchemaIndex) CanonicalPaths() []string {
paths := make([]string, 0, len(i.byCanonical))
for path := range i.byCanonical {
paths = append(paths, path)
}
sort.Strings(paths)
return paths
}
// ToSnapshotPayload derives the progressive Catalog and full tool map from one
// normalized registry. This is the direct typed replacement for separately
// assembling runtimeToolSummary and runtimeToolPayload.
func (r SchemaRegistry) ToSnapshotPayload() (SchemaSnapshotPayload, error) {
index, err := r.Index()
if err != nil {
return SchemaSnapshotPayload{}, err
}
r = index.Registry()
products := make([]map[string]any, 0, len(r.Products))
tools := make(map[string]map[string]any, len(index.byCanonical))
for _, product := range r.Products {
summaries := make([]map[string]any, 0, len(product.Tools))
for _, tool := range product.Tools {
summary, summaryErr := snapshotToolSummary(tool)
if summaryErr != nil {
return SchemaSnapshotPayload{}, summaryErr
}
full, fullErr := snapshotToolPayload(tool)
if fullErr != nil {
return SchemaSnapshotPayload{}, fullErr
}
summaries = append(summaries, summary)
tools[tool.Identity.CanonicalPath] = full
}
productPayload, productErr := snapshotProductSummary(product)
if productErr != nil {
return SchemaSnapshotPayload{}, productErr
}
productPayload["tools"] = summaries
productPayload["tool_count"] = len(summaries)
products = append(products, productPayload)
}
catalog, err := r.registryEnvelopePayload(products, len(tools))
if err != nil {
return SchemaSnapshotPayload{}, err
}
return SchemaSnapshotPayload{Catalog: catalog, Tools: tools}, nil
}
// registryEnvelopePayload renders the shared Catalog envelope around prebuilt
// product payloads. The snapshot catalog and schema --all projection both use
// it, so the envelope keys cannot drift between the two views.
func (r SchemaRegistry) registryEnvelopePayload(products []map[string]any, toolCount int) (map[string]any, error) {
payload := map[string]any{}
payload["kind"] = defaultString(r.Kind, "schema")
payload["level"] = defaultString(r.Level, "catalog")
payload["count"] = len(products)
payload["tool_count"] = toolCount
payload["products"] = products
if r.Source != "" {
payload["source"] = r.Source
}
if err := putRawJSON(payload, "agent_metadata", r.AgentMetadata); err != nil {
return nil, fmt.Errorf("agent_metadata: %w", err)
}
return payload, nil
}
// Validate checks structural invariants only. It intentionally does not
// constrain precedence outcomes such as risk or required values.
func (t ToolSpec) Validate() error {
id := t.Identity
if id.ProductID == "" {
return fmt.Errorf("tool product_id is empty")
}
if id.Name == "" {
return fmt.Errorf("tool name is empty")
}
wantCanonical := id.ProductID + "." + id.Name
if id.CanonicalPath != wantCanonical {
return fmt.Errorf("tool canonical_path %q does not match %q", id.CanonicalPath, wantCanonical)
}
if id.CLIPath == "" {
return fmt.Errorf("tool %s cli_path is empty", id.CanonicalPath)
}
seen := make(map[string]bool, len(t.Parameters))
for _, parameter := range t.Parameters {
if parameter.Name == "" {
return fmt.Errorf("tool %s has parameter with empty name", id.CanonicalPath)
}
if seen[parameter.Name] {
return fmt.Errorf("tool %s has duplicate parameter %q", id.CanonicalPath, parameter.Name)
}
seen[parameter.Name] = true
if err := validateRawJSON(parameter.Default, "default", id.CanonicalPath, parameter.Name); err != nil {
return err
}
if err := validateRawJSON(parameter.InterfaceDefault, "interface_default", id.CanonicalPath, parameter.Name); err != nil {
return err
}
if err := validateRawJSON(parameter.Example, "example", id.CanonicalPath, parameter.Name); err != nil {
return err
}
}
if t.Interface.Ref != nil {
if strings.TrimSpace(t.Interface.Ref.ProductID) == "" || strings.TrimSpace(t.Interface.Ref.RPCName) == "" {
return fmt.Errorf("tool %s has incomplete interface_ref", id.CanonicalPath)
}
}
if t.DryRun != nil {
if err := t.DryRun.Validate(id.CanonicalPath); err != nil {
return err
}
}
if t.Result != nil {
if _, err := contract.NormalizeResultSpec(t.Result, id.CanonicalPath); err != nil {
return err
}
}
if t.Pagination != nil {
pagination, err := contract.NormalizePaginationSpec(t.Pagination, id.CanonicalPath)
if err != nil {
return err
}
if !seen[pagination.CursorParameter] {
return fmt.Errorf("tool %s pagination cursor_parameter %q is not a declared parameter", id.CanonicalPath, pagination.CursorParameter)
}
}
if t.Interface.Mode != "" || t.Interface.Availability != "" || t.Interface.Reason != "" || t.Interface.Ref != nil {
if err := t.Interface.Validate(id.CanonicalPath); err != nil {
return err
}
}
for field, provenance := range t.FieldProvenance {
if value, ok := t.provenanceValue(field); ok {
if err := validateFinalFieldProvenance(id.CanonicalPath, field, provenance, value); err != nil {
return err
}
}
}
for _, parameter := range t.Parameters {
for field, provenance := range parameter.FieldProvenance {
if value, ok := parameter.provenanceValue(field); ok {
if err := validateFinalFieldProvenance(id.CanonicalPath+" parameter "+parameter.Name, field, provenance, value); err != nil {
return err
}
}
}
}
return nil
}
func validateFinalFieldProvenance(owner, field string, provenance contract.FieldProvenance, finalValue any) error {
expected, err := json.Marshal(finalValue)
if err != nil {
return fmt.Errorf("%s field %s cannot encode final provenance value: %w", owner, field, err)
}
if len(provenance.Value) == 0 || !json.Valid(provenance.Value) || !equalJSONValues(provenance.Value, expected) {
return fmt.Errorf("%s field %s provenance winner does not equal final value: winner=%s final=%s", owner, field, string(provenance.Value), string(expected))
}
if strings.TrimSpace(provenance.Source) == "" || strings.TrimSpace(provenance.Precedence) == "" || strings.TrimSpace(provenance.Resolution) == "" {
return fmt.Errorf("%s field %s provenance winner is incomplete", owner, field)
}
if len(provenance.Candidates) == 0 {
return fmt.Errorf("%s field %s provenance has no candidates", owner, field)
}
selected := 0
for _, candidate := range provenance.Candidates {
if len(candidate.Value) == 0 || !json.Valid(candidate.Value) {
return fmt.Errorf("%s field %s has a provenance candidate with invalid value", owner, field)
}
if candidate.Selected == nil || !*candidate.Selected {
continue
}
selected++
if !json.Valid(candidate.Value) || !equalJSONValues(candidate.Value, expected) {
return fmt.Errorf("%s field %s selected provenance candidate does not equal final value", owner, field)
}
if strings.TrimSpace(candidate.Source) != strings.TrimSpace(provenance.Source) ||
strings.TrimSpace(candidate.Precedence) != strings.TrimSpace(provenance.Precedence) {
return fmt.Errorf("%s field %s selected provenance candidate disagrees with winner", owner, field)
}
}
for _, candidate := range provenance.OverriddenCandidates {
if len(candidate.Value) == 0 || !json.Valid(candidate.Value) {
return fmt.Errorf("%s field %s has an overridden provenance candidate with invalid value", owner, field)
}
if candidate.Selected != nil && *candidate.Selected {
return fmt.Errorf("%s field %s has a selected overridden provenance candidate", owner, field)
}
}
if selected != 1 {
return fmt.Errorf("%s field %s provenance has %d selected candidates, want 1", owner, field, selected)
}
return nil
}
// equalJSONValues compares decoded JSON types and values while preserving the
// lexical representation of numbers through json.Number. Formatting and
// equivalent string escapes are intentionally ignored; 1 and 1.0 remain
// distinct provenance values.
func equalJSONValues(left, right []byte) bool {
// Generated Schema values are already canonical JSON in the common path.
// Avoid allocating two decoded object graphs for identical bytes; keep the
// semantic decoder fallback for whitespace, escape, and representation
// differences. json.Valid preserves the previous behavior for equal but
// malformed input.
if bytes.Equal(left, right) {
return json.Valid(left)
}
decode := func(raw []byte) (any, bool) {
if !json.Valid(raw) {
return nil, false
}
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
var value any
_ = decoder.Decode(&value)
return value, true
}
leftValue, leftOK := decode(left)
rightValue, rightOK := decode(right)
return leftOK && rightOK && reflect.DeepEqual(leftValue, rightValue)
}
func validateRawJSON(value json.RawMessage, field, canonical, parameter string) error {
if len(value) == 0 || json.Valid(value) {
return nil
}
return fmt.Errorf("tool %s parameter %q has invalid JSON %s", canonical, parameter, field)
}
// Sorted returns a detached, deterministic registry. Products are ordered by
// ID, tools by canonical path then CLI path, and parameters by CLI flag name.
func (r SchemaRegistry) Sorted() SchemaRegistry {
out := r
out.Products = append([]ProductSpec(nil), r.Products...)
for i := range out.Products {
out.Products[i] = out.Products[i].normalized()
}
sort.Slice(out.Products, func(i, j int) bool {
return out.Products[i].ID < out.Products[j].ID
})
return out
}
func (p ProductSpec) normalized() ProductSpec {
out := p
out.ID = strings.TrimSpace(out.ID)
out.Name = strings.TrimSpace(out.Name)
out.Tools = append([]ToolSpec(nil), p.Tools...)
for i := range out.Tools {
out.Tools[i] = out.Tools[i].normalized()
}
sort.Slice(out.Tools, func(i, j int) bool {
left, right := out.Tools[i].Identity, out.Tools[j].Identity
if left.CanonicalPath != right.CanonicalPath {
return left.CanonicalPath < right.CanonicalPath
}
return left.CLIPath < right.CLIPath
})
out.Selection = out.Selection.Normalized()
out.FieldProvenance = cloneFieldProvenance(out.FieldProvenance)
return out
}
func (p ProductSpec) provenanceValue(field string) (any, bool) {
switch field {
case "agent_summary":
return p.Selection.AgentSummary, true
case "use_when":
return p.Selection.UseWhen, true
case "avoid_when":
return p.Selection.AvoidWhen, true
default:
return nil, false
}
}
func (t ToolSpec) normalized() ToolSpec {
out := t
id := out.Identity
id.ProductID = strings.TrimSpace(id.ProductID)
id.SourceProductID = strings.TrimSpace(id.SourceProductID)
id.Name = strings.TrimSpace(id.Name)
id.CLIName = strings.TrimSpace(id.CLIName)
id.CanonicalPath = strings.TrimSpace(id.CanonicalPath)
if id.CanonicalPath == "" && id.ProductID != "" && id.Name != "" {
id.CanonicalPath = id.ProductID + "." + id.Name
}
id.Path = strings.TrimSpace(id.Path)
if id.Path == "" {
id.Path = id.CanonicalPath
}
id.CLIPath = normalizeSchemaCLIPath(id.CLIPath)
id.PrimaryCLIPath = normalizeSchemaCLIPath(id.PrimaryCLIPath)
if id.PrimaryCLIPath == "" {
id.PrimaryCLIPath = id.CLIPath
}
id.Group = strings.TrimSpace(id.Group)
id.Source = strings.TrimSpace(id.Source)
id.Aliases = sortedUniqueStrings(id.Aliases)
out.Identity = id
out.Parameters = append([]ParameterSpec(nil), t.Parameters...)
for i := range out.Parameters {
out.Parameters[i] = out.Parameters[i].normalized()
}
sort.Slice(out.Parameters, func(i, j int) bool {
return out.Parameters[i].Name < out.Parameters[j].Name
})
out.Constraints = normalizeRuntimeSchemaConstraints(t.Constraints)
if t.DryRun != nil {
dryRun := *t.DryRun
dryRun.PreviewKind = strings.TrimSpace(dryRun.PreviewKind)
out.DryRun = &dryRun
}
if t.Result != nil {
result, err := contract.NormalizeResultSpec(t.Result, id.CanonicalPath)
if err == nil {
out.Result = result
}
}
if t.Pagination != nil {
pagination, err := contract.NormalizePaginationSpec(t.Pagination, id.CanonicalPath)
if err == nil {
out.Pagination = pagination
}
}
out.Positionals = append([]contract.RuntimeSchemaPositional(nil), t.Positionals...)
sort.Slice(out.Positionals, func(i, j int) bool {
if out.Positionals[i].Index != out.Positionals[j].Index {
return out.Positionals[i].Index < out.Positionals[j].Index
}
return out.Positionals[i].Name < out.Positionals[j].Name
})
out.Selection = out.Selection.Normalized()
return out
}
func (p ParameterSpec) normalized() ParameterSpec {
out := p
out.Name = strings.TrimSpace(out.Name)
out.Type = strings.TrimSpace(out.Type)
out.Property = strings.TrimSpace(out.Property)
out.Enum = stableUniqueStrings(out.Enum)
return out
}
func stableUniqueStrings(values []string) []string {
if values == nil {
return nil
}
seen := make(map[string]bool, len(values))
out := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" || seen[value] {
continue
}
seen[value] = true
out = append(out, value)
}
return out
}
// cloneOptionalStrings preserves the semantic difference between an omitted
// list (nil) and an explicitly authored empty list (non-nil, length zero).
// Selection precedence treats [] as a real winner, so that presence must
// survive the generated metadata adapter and every typed projection.
func cloneOptionalStrings(values []string) []string {
if values == nil {
return nil
}
out := make([]string, len(values))
copy(out, values)
return out
}
func sortedUniqueStrings(values []string) []string {
seen := make(map[string]bool, len(values))
out := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" || seen[value] {
continue
}
seen[value] = true
out = append(out, value)
}
sort.Strings(out)
if len(out) == 0 {
return nil
}
return out
}
// ToPayload renders the compatible flat schema payload after deterministically
// sorting the typed model.
func (r SchemaRegistry) ToPayload() (map[string]any, error) {
index, err := r.Index()
if err != nil {
return nil, err
}
r = index.Registry()
products := make([]map[string]any, 0, len(r.Products))
toolCount := 0
for _, product := range r.Products {
payload, err := product.ToPayload()
if err != nil {
return nil, err
}
products = append(products, payload)
toolCount += len(product.Tools)
}
return r.registryEnvelopePayload(products, toolCount)
}
// ToOverviewPayload renders the small first-hop product index directly from
// the resolved registry. It is a presentation projection only: it never reads
// Agent metadata or any other source after assembly.
func (r SchemaRegistry) ToOverviewPayload() (map[string]any, error) {
index, err := r.Index()
if err != nil {
return nil, err
}
r = index.Registry()
products := make([]map[string]any, 0, len(r.Products))
toolCount := 0
for _, product := range r.Products {
entry := map[string]any{
"id": product.ID,
"tool_count": len(product.Tools),
"schema_path": product.ID,
}
if summary := strings.TrimSpace(product.Selection.AgentSummary); summary != "" {
entry["agent_summary"] = summary
} else if len(product.Selection.UseWhen) > 0 {
entry["use_when"] = []string{product.Selection.UseWhen[0]}
} else if product.Description != "" {
entry["description"] = product.Description
}
products = append(products, entry)
toolCount += len(product.Tools)
}
payload := map[string]any{
"kind": defaultString(r.Kind, "schema"),
"level": "products",
"count": len(products),
"tool_count": toolCount,
"products": products,
}
if r.Source != "" {
payload["source"] = r.Source
}
if err := putRawJSON(payload, "agent_metadata", r.AgentMetadata); err != nil {
return nil, fmt.Errorf("agent_metadata: %w", err)
}
return payload, nil
}
// ToPayload renders one product and its full tools.
func (p ProductSpec) ToPayload() (map[string]any, error) {
return p.envelopePayload(ToolSpec.ToPayload)
}
// ToSummaryPayload renders one product with progressive tool summaries. Both
// this view and the full product payload are projections of the same ToolSpec
// slice; neither re-resolves annotations or metadata.
func (p ProductSpec) ToSummaryPayload() (map[string]any, error) {
return p.envelopePayload(ToolSpec.ToSummaryPayload)
}
// envelopePayload renders the product envelope around tools rendered by
// renderTool. The full and summary product payloads differ only in the tool
// renderer, which keeps the envelope keys byte-identical across projections.
func (p ProductSpec) envelopePayload(renderTool func(ToolSpec) (map[string]any, error)) (map[string]any, error) {
p = p.normalized()
tools := make([]map[string]any, 0, len(p.Tools))
for _, tool := range p.Tools {
payload, err := renderTool(tool)
if err != nil {
return nil, err
}
tools = append(tools, payload)
}
payload := map[string]any{}
payload["id"] = p.ID
payload["name"] = p.Name
payload["description"] = p.Description
payload["tool_count"] = len(tools)
payload["tools"] = tools
if p.Runtime {
payload["runtime"] = true
}
applySelectionPayload(payload, p.Selection, false)
if len(p.FieldProvenance) > 0 {
value, valueErr := typedJSONValue(p.FieldProvenance)
if valueErr != nil {
return nil, valueErr
}
payload["field_provenance"] = value
}
return payload, nil
}
// ToPayload renders one full leaf contract in the existing flat JSON shape.
func (t ToolSpec) ToPayload() (map[string]any, error) {
t = t.normalized()
if err := t.Validate(); err != nil {
return nil, err
}
payload := map[string]any{}
id := t.Identity
payload["name"] = id.Name
payload["cli_name"] = id.CLIName
payload["canonical_path"] = id.CanonicalPath
payload["path"] = id.Path
payload["cli_path"] = id.CLIPath
payload["primary_cli_path"] = id.PrimaryCLIPath
payload["is_alias"] = id.IsAlias
payload["source"] = id.Source
payload["product_id"] = id.ProductID
payload["display"] = t.Display
payload["title"] = t.Title
payload["description"] = t.Description
setOptionalString(payload, "source_product_id", id.SourceProductID)
setOptionalString(payload, "group", id.Group)
setOptionalString(payload, "metadata_source", t.MetadataSource)
if len(id.Aliases) > 0 {
payload["aliases"] = append([]string(nil), id.Aliases...)
}
parameters := make(map[string]any, len(t.Parameters))
for _, parameter := range t.Parameters {
entry, parameterErr := parameter.ToPayload()
if parameterErr != nil {
return nil, fmt.Errorf("render %s parameter %s: %w", id.CanonicalPath, parameter.Name, parameterErr)
}
parameters[parameter.Name] = entry
}
payload["parameters"] = parameters
payload["has_parameters"] = len(parameters) > 0
payload["parameter_count"] = len(parameters)
if !runtimeSchemaConstraintsEmpty(t.Constraints) {
value, _ := typedJSONValue(t.Constraints)
payload["constraints"] = value
}
if len(t.Positionals) > 0 {
value, _ := typedJSONValue(t.Positionals)
payload["positionals"] = value
}
if t.DryRun != nil {
value, _ := typedJSONValue(t.DryRun)
payload["dry_run"] = value
}
if t.Result != nil {
value, _ := typedJSONValue(t.Result)
payload["result"] = value
}
if t.Pagination != nil {
value, _ := typedJSONValue(t.Pagination)
payload["pagination"] = value
}
applySafetyPayload(payload, t.Safety)
applyInterfacePayload(payload, t.Interface)
applySelectionPayload(payload, t.Selection, true)
if len(t.FieldProvenance) > 0 {
value, valueErr := typedJSONValue(t.FieldProvenance)
if valueErr != nil {
return nil, valueErr
}
payload["field_provenance"] = value
}
return payload, nil
}
// ToSummaryPayload projects a ToolSpec without parameters and leaf-only
// execution detail. It is derived from the same typed ToolSpec, never assembled
// independently.
func (t ToolSpec) ToSummaryPayload() (map[string]any, error) {
payload, err := t.ToPayload()
if err != nil {
return nil, err
}
for _, key := range []string{
"parameters", "has_parameters", "parameter_count", "constraints",
"positionals", "result", "examples", "effect_source", "agent_source_refs",
"field_provenance", "path", "source", "product_id", "display", "is_alias",
} {
delete(payload, key)
}
return payload, nil
}
// ToPayload renders one parameter in the existing parameters.<flag> shape.
func (p ParameterSpec) ToPayload() (map[string]any, error) {
p = p.normalized()
payload := map[string]any{}
payload["type"] = p.Type
payload["description"] = p.Description
payload["required"] = p.Required
setOptionalString(payload, "property", p.Property)
if p.CLIRequired {
payload["cli_required"] = true
}
setOptionalString(payload, "required_when", p.RequiredWhen)
if err := putRawJSON(payload, "default", p.Default); err != nil {
return nil, err
}
if err := putRawJSON(payload, "interface_default", p.InterfaceDefault); err != nil {
return nil, err
}
if err := putRawJSON(payload, "example", p.Example); err != nil {
return nil, err
}
setOptionalString(payload, "format", p.Format)
if len(p.Enum) > 0 {
payload["enum"] = append([]string(nil), p.Enum...)
}
setOptionalString(payload, "interface_description", p.InterfaceDescription)
setOptionalString(payload, "interface_type", p.InterfaceType)
if len(p.FieldProvenance) > 0 {
value, valueErr := typedJSONValue(p.FieldProvenance)
if valueErr != nil {
return nil, valueErr
}
payload["field_provenance"] = value
}
return payload, nil
}
func applySafetyPayload(payload map[string]any, safety contract.SafetySpec) {
setOptionalString(payload, "effect", safety.Effect)
setOptionalString(payload, "effect_source", safety.EffectSource)
setOptionalString(payload, "risk", safety.Risk)
setOptionalString(payload, "confirmation", safety.Confirmation)
setOptionalString(payload, "idempotency", safety.Idempotency)
}
func applyInterfacePayload(payload map[string]any, spec contract.InterfaceSpec) {
if spec.Ref != nil {
payload["interface_ref"] = map[string]any{
"product_id": spec.Ref.ProductID,
"rpc_name": spec.Ref.RPCName,
}
}
setOptionalString(payload, "interface_mode", spec.Mode)
setOptionalString(payload, "availability", spec.Availability)
setOptionalString(payload, "interface_reason", spec.Reason)
}
func applySelectionPayload(payload map[string]any, selection contract.SelectionSpec, full bool) {
setOptionalString(payload, "agent_summary", selection.AgentSummary)
setOptionalString(payload, "agent_summary_source", selection.AgentSummarySource)
setOptionalStrings(payload, "use_when", selection.UseWhen)
setOptionalStrings(payload, "avoid_when", selection.AvoidWhen)
if full {
setOptionalStrings(payload, "prerequisites", selection.Prerequisites)
setOptionalStrings(payload, "tips", selection.Tips)
setOptionalStrings(payload, "workflow_refs", selection.WorkflowRefs)
setOptionalStrings(payload, "examples", selection.Examples)
if selection.Reviewed != nil {
payload["reviewed"] = *selection.Reviewed
}
}
setOptionalStrings(payload, "agent_source_refs", selection.SourceRefs)
setOptionalString(payload, "agent_metadata_source", selection.MetadataSource)
}
func setOptionalString(payload map[string]any, key, value string) {
if value = strings.TrimSpace(value); value != "" {
payload[key] = value
}
}
func setOptionalStrings(payload map[string]any, key string, values []string) {
if values = stableUniqueStrings(values); values != nil {
payload[key] = values
}
}
func defaultString(value, fallback string) string {
if value = strings.TrimSpace(value); value != "" {
return value
}
return fallback
}
func putRawJSON(payload map[string]any, key string, raw json.RawMessage) error {
if len(raw) == 0 {
return nil
}
value, err := rawJSONValue(raw)
if err != nil {
return err
}
payload[key] = value
return nil
}
// rawJSONValue decodes a JSON value that may come from an untrusted source, so
// it validates before decoding. Callers holding output that json.Marshal just
// produced should use typedJSONValue instead of paying the validation scan.
func rawJSONValue(raw json.RawMessage) (any, error) {
if !json.Valid(raw) {
return nil, fmt.Errorf("invalid JSON value")
}
return decodeValidJSONValue(raw), nil
}
// decodeValidJSONValue decodes JSON whose validity the caller has already
// established, either by json.Valid or by having just marshaled it. Decode
// errors are unreachable under that precondition and are therefore discarded,
// exactly as this path behaved when the decode was inlined into rawJSONValue.
func decodeValidJSONValue(raw json.RawMessage) any {
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
var value any
_ = decoder.Decode(&value)
return value
}
// typedJSONValue projects a typed value into the generic JSON shape the payload
// renderers consume. json.Marshal output is valid by construction, so this path
// decodes it directly: routing through rawJSONValue re-scanned every marshaled
// document with json.Valid, which measured ~34% of Schema Catalog assembly time
// across the 1121-tool set (26.0s -> 17.2s for the internal/app schema suite).
func typedJSONValue(value any) (any, error) {
data, err := json.Marshal(value)
if err != nil {
return nil, err
}
return decodeValidJSONValue(data), nil
}