fix(schema): align CR #7 declare≡execute constraints with gates
Update shortcut/app expectations, catalog jq, and schema-compat to accept full hidden-sibling constraint groups, and bump delivered shortcut count to 216. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -161,14 +161,21 @@ if ! jq -e '
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# declare≡execute may project hidden execute-side siblings into constraint
|
||||
# groups. Require every group to touch at least one published parameter/positional;
|
||||
# unpublished members are allowed as hidden companions.
|
||||
if ! jq -e '
|
||||
[.tools[] | select(.constraints != null)] as $tools |
|
||||
($tools | length) >= 21 and
|
||||
all($tools[];
|
||||
(((.parameters // {}) | keys) + ((.positionals // []) | map(.name))) as $names |
|
||||
all((.constraints.mutually_exclusive // [])[]; length > 1 and all(.[]; IN($names[]))) and
|
||||
all((.constraints.require_one_of // [])[]; length > 1 and all(.[]; IN($names[]))) and
|
||||
all((.constraints.require_together // [])[]; length > 1 and all(.[]; IN($names[])))
|
||||
def ok_group:
|
||||
length > 1 and
|
||||
all(.[]; type == "string" and length > 0) and
|
||||
any(.[]; IN($names[]));
|
||||
all((.constraints.mutually_exclusive // [])[]; ok_group) and
|
||||
all((.constraints.require_one_of // [])[]; ok_group) and
|
||||
all((.constraints.require_together // [])[]; ok_group)
|
||||
)
|
||||
' "$catalog" >/dev/null; then
|
||||
printf '%s\n' 'schema command constraints are incomplete or reference unknown parameters' >&2
|
||||
|
||||
@@ -481,7 +481,6 @@ func checkToolCompatibility(toolPath string, oldTool, newTool toolSchema) []stri
|
||||
{name: "interface_mode", old: oldTool.InterfaceMode, new: newTool.InterfaceMode},
|
||||
{name: "interface_ref", old: oldTool.InterfaceRef, new: newTool.InterfaceRef},
|
||||
{name: "availability", old: oldTool.Availability, new: newTool.Availability},
|
||||
{name: "constraints", old: oldTool.Constraints, new: newTool.Constraints},
|
||||
{name: "effect", old: oldTool.Effect, new: newTool.Effect},
|
||||
{name: "risk", old: oldTool.Risk, new: newTool.Risk},
|
||||
{name: "confirmation", old: oldTool.Confirmation, new: newTool.Confirmation},
|
||||
@@ -491,6 +490,10 @@ func checkToolCompatibility(toolPath string, oldTool, newTool toolSchema) []stri
|
||||
failures = append(failures, fmt.Sprintf("schema tool %q changed %s", toolPath, field.name))
|
||||
}
|
||||
}
|
||||
if oldTool.Constraints != newTool.Constraints &&
|
||||
!compatibleHiddenSiblingConstraintExpansion(oldTool, newTool) {
|
||||
failures = append(failures, fmt.Sprintf("schema tool %q changed constraints", toolPath))
|
||||
}
|
||||
if !compatiblePositionals(oldTool.Positionals, newTool.Positionals) {
|
||||
failures = append(failures, fmt.Sprintf("schema tool %q changed positionals", toolPath))
|
||||
}
|
||||
@@ -510,6 +513,66 @@ func checkToolCompatibility(toolPath string, oldTool, newTool toolSchema) []stri
|
||||
return failures
|
||||
}
|
||||
|
||||
// compatibleHiddenSiblingConstraintExpansion allows declare≡execute repairs:
|
||||
// Schema may start projecting full constraint groups that include unpublished
|
||||
// (hidden) execute-side siblings when the previous contract collapsed the sole
|
||||
// published member to required and omitted constraints.
|
||||
func compatibleHiddenSiblingConstraintExpansion(oldTool, newTool toolSchema) bool {
|
||||
if strings.TrimSpace(oldTool.Constraints) != "" {
|
||||
return false
|
||||
}
|
||||
var projected struct {
|
||||
MutuallyExclusive [][]string `json:"mutually_exclusive"`
|
||||
RequireOneOf [][]string `json:"require_one_of"`
|
||||
RequireTogether [][]string `json:"require_together"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(newTool.Constraints), &projected); err != nil {
|
||||
return false
|
||||
}
|
||||
if len(projected.RequireTogether) > 0 || len(projected.RequireOneOf) == 0 {
|
||||
return false
|
||||
}
|
||||
groups := append([][]string(nil), projected.RequireOneOf...)
|
||||
groups = append(groups, projected.MutuallyExclusive...)
|
||||
for _, group := range groups {
|
||||
if len(group) < 2 {
|
||||
return false
|
||||
}
|
||||
published := 0
|
||||
hidden := 0
|
||||
for _, name := range group {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
if _, ok := newTool.Parameters[name]; ok {
|
||||
published++
|
||||
} else {
|
||||
hidden++
|
||||
}
|
||||
}
|
||||
if published == 0 || hidden == 0 {
|
||||
return false
|
||||
}
|
||||
// Former collapse artifact: exactly one published member was required.
|
||||
if published == 1 {
|
||||
var sole string
|
||||
for _, name := range group {
|
||||
if _, ok := newTool.Parameters[name]; ok {
|
||||
sole = name
|
||||
break
|
||||
}
|
||||
}
|
||||
oldParam, ok := oldTool.Parameters[sole]
|
||||
newParam, okNew := newTool.Parameters[sole]
|
||||
if !ok || !okNew || !oldParam.Required || newParam.Required {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func compatiblePositionals(oldPositionals, newPositionals []positionalSchema) bool {
|
||||
if len(newPositionals) < len(oldPositionals) {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user