Files
dingtalk-workspace-cli/scripts/policy/check-schema-binary.sh
T
玉澜 51dc237d8a feat: declarative LeafSpec command framework + schema generation/consumption separation
== LeafSpec command framework (internal/helpers/leaf.go) ==
Declarative command construction: LeafSpec/LeafFlag/NewLeafCommand with
Call (pluggable dispatch), LeafInt, PostMount, Trim, Validate. Collapses
per-command hand-written required validation, alias/env fallback, value
transform, and toolArgs assembly into one declarative path.

== devapp migration (28/31 commands) ==
All MCP-direct devapp leaf commands migrated to LeafSpec. Factories
(devAppCall/devAppCallCursor/devAppMeta) fold 33 repeated closures.
fakeDevAppRunner asserts toolArgs for every migrated command. 4 complex
commands (delete/robot submit/result/config) kept hand-written.

== Schema generation/consumption separation ==
- gen.go: isolated //go:generate pragmas from business code.
- command_meta.go: ResolveMeta(cliPath) -> CommandMeta{Identity,Safety,Selection}.
- command_safety.go: SafetyForCLIPath + RenderSafetyAnnotation; safety metadata
  flows from embedded catalog into --help output.
- calendar.go HelpFunc fix: delegates to root HelpFunc at help-time.
- schema_catalog_structure.go: closed catalog structure validation gate.

== Registry + catalog per-product sharding ==
schema_command_registry and schema_catalog split into per-product shards,
eliminating concurrent-PR merge conflicts on these files.

== MCP metadata refresh tool ==
cmd/fetch_mcp_metadata: iterates 26 MCP server endpoints, merges with previous
data for cross-server interface_ref. make fetch-mcp-metadata target.

== AGENTS.md ==
Documents the generation/consumption split.

Verified: make policy exit 0, drift zero, all tests pass.
2026-07-19 09:38:43 +08:00

165 lines
7.1 KiB
Bash
Executable File

#!/bin/sh
set -eu
ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
cd "$ROOT"
BIN="${DWS_BIN:-$ROOT/dws}"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
# The release Catalog is committed as a per-product split; reassemble it into
# the single-document shape this check consumes.
catalog_combined="$tmp/catalog-combined.json"
scripts/policy/with-catalog.sh >"$catalog_combined"
if [ ! -x "$BIN" ]; then
printf 'error: dws binary not found at %s (run make build first)\n' "$BIN" >&2
exit 2
fi
go run ./internal/generator/cmd_schema_registry_smoke >"$tmp/smoke-vector.json"
"$BIN" schema list --format json >"$tmp/list.json"
"$BIN" schema --all --format json >"$tmp/all.json"
if ! jq -e '
.version == 1 and
(.registry_hash | type) == "string" and
(.registry_hash | test("^sha256:[0-9a-f]{64}$")) and
(.commands | type) == "array" and
(.commands | length) > 0 and
((.commands | map(.canonical_path) | unique | length) == (.commands | length)) and
((.commands | map(.primary_cli_path) | unique | length) == (.commands | length)) and
all(.commands[];
(.canonical_path | type) == "string" and (.canonical_path | length) > 0 and
(.primary_cli_path | type) == "string" and (.primary_cli_path | length) > 0 and
(.alias_cli_paths | type) == "array" and
((.alias_cli_paths | unique | length) == (.alias_cli_paths | length)))
' "$tmp/smoke-vector.json" >/dev/null; then
printf '%s\n' 'EffectiveCommandRegistry full smoke vector is malformed or empty' >&2
exit 1
fi
if ! jq -e -n \
--slurpfile vector "$tmp/smoke-vector.json" \
--slurpfile catalog "$catalog_combined" \
--slurpfile list "$tmp/list.json" \
--slurpfile all "$tmp/all.json" '
$vector[0] as $vector |
$catalog[0] as $catalog |
$list[0] as $list |
$all[0] as $all |
$vector.registry_hash == $catalog.surface_hash and
$list.surface_hash == $vector.registry_hash and
$all.surface_hash == $vector.registry_hash and
$list.catalog_hash == $catalog.source_hash and
$all.catalog_hash == $catalog.source_hash and
($vector.commands | map(.canonical_path) | sort) == ($catalog.tools | keys | sort) and
($vector.commands | map(.canonical_path) | sort) ==
([$all.products[].tools[].canonical_path] | sort) and
([$vector.commands[] as $command |
($catalog.tools[$command.canonical_path].primary_cli_path == $command.primary_cli_path and
(($catalog.tools[$command.canonical_path].aliases // []) | sort) ==
($command.alias_cli_paths | sort))] | all) and
all($all.products[].tools[]; . == $catalog.tools[.canonical_path])
' >/dev/null; then
printf '%s\n' 'built dws Schema navigation/hash/--all content differs from EffectiveCommandRegistry or embedded Catalog' >&2
exit 1
fi
# schema list is an intentionally compact product overview, but every field
# still has to be the exact typed projection of the same embedded Catalog.
# Derive the expected overview without invoking another Schema code path so a
# release binary cannot hide loader/query drift behind two identical helpers.
jq -S '
def nonempty: type == "string" and (gsub("^\\s+|\\s+$"; "") | length) > 0;
. as $snapshot |
$snapshot.catalog as $catalog |
{
kind: (if ($catalog.kind | nonempty) then $catalog.kind else "schema" end),
level: "products",
count: ($catalog.products | length),
tool_count: ($catalog.products | map(.tool_count) | add // 0),
products: [
$catalog.products[] |
. as $product |
{
id: $product.id,
tool_count: $product.tool_count,
schema_path: $product.id
} +
if (($product.agent_summary // "") | nonempty) then
{agent_summary: $product.agent_summary}
elif (($product.use_when // []) | length) > 0 then
{use_when: [$product.use_when[0]]}
elif (($product.description // "") | type == "string" and length > 0) then
{description: $product.description}
else {} end
]
} +
(if (($catalog.source // "") | type == "string" and length > 0) then {source: $catalog.source} else {} end) +
(if $catalog | has("interface_metadata") then {interface_metadata: $catalog.interface_metadata} else {} end) +
(if $catalog | has("agent_metadata") then {agent_metadata: $catalog.agent_metadata} else {} end) +
{catalog_hash: $snapshot.source_hash} +
(if (($snapshot.surface_hash // "") | nonempty) then {surface_hash: $snapshot.surface_hash} else {} end)
' "$catalog_combined" >"$tmp/expected-list.json"
jq -S . "$tmp/list.json" >"$tmp/actual-list.json"
if ! cmp -s "$tmp/expected-list.json" "$tmp/actual-list.json"; then
printf '%s\n' 'built dws schema list differs from the typed Catalog overview projection' >&2
diff -u "$tmp/expected-list.json" "$tmp/actual-list.json" || true
exit 1
fi
# The exact public EffectiveCommandRegistry set and every alias mapping were compared above using the
# release binary's --all payload. Exercise representative canonical/primary
# query routes here; exhaustive per-path resolution runs in the in-process Go
# delivery gate, avoiding more than one thousand cold binary startups in CI.
jq -c '.commands as $commands | [$commands[0], $commands[($commands|length)/2|floor], $commands[-1]] | unique_by(.canonical_path)[]' \
"$tmp/smoke-vector.json" >"$tmp/commands.jsonl"
while IFS= read -r command; do
canonical="$(printf '%s\n' "$command" | jq -r '.canonical_path')"
primary="$(printf '%s\n' "$command" | jq -r '.primary_cli_path')"
"$BIN" schema "$canonical" --format json >"$tmp/canonical.json"
"$BIN" schema --cli-path "$primary" --format json >"$tmp/primary.json"
jq -S --arg canonical "$canonical" '
[.products[].tools[] | select(.canonical_path == $canonical)] |
if length == 1 then .[0] else null end
' "$tmp/all.json" >"$tmp/all-tool.json"
jq -S . "$tmp/canonical.json" >"$tmp/canonical-sorted.json"
jq -S . "$tmp/primary.json" >"$tmp/primary-sorted.json"
if ! cmp -s "$tmp/canonical-sorted.json" "$tmp/primary-sorted.json" ||
! cmp -s "$tmp/canonical-sorted.json" "$tmp/all-tool.json"; then
printf 'built dws schema canonical/primary/--all payloads differ: %s\n' "$canonical" >&2
exit 1
fi
done <"$tmp/commands.jsonl"
alias_command="$(jq -c 'first(.commands[] | select((.alias_cli_paths | length) > 0)) // empty' "$tmp/smoke-vector.json")"
if [ -n "$alias_command" ]; then
canonical="$(printf '%s\n' "$alias_command" | jq -r '.canonical_path')"
alias_path="$(printf '%s\n' "$alias_command" | jq -r '.alias_cli_paths[0]')"
"$BIN" schema "$canonical" --format json >"$tmp/canonical.json"
"$BIN" schema --cli-path "$alias_path" --format json >"$tmp/alias.json"
jq -S 'del(.cli_path, .is_alias)' "$tmp/canonical.json" >"$tmp/canonical-content.json"
jq -S 'del(.cli_path, .is_alias)' "$tmp/alias.json" >"$tmp/alias-content.json"
if ! cmp -s "$tmp/canonical-content.json" "$tmp/alias-content.json"; then
printf 'built dws schema alias changes fields other than cli_path/is_alias: %s\n' "$alias_path" >&2
exit 1
fi
if ! jq -e --arg canonical "$canonical" --arg alias_path "$alias_path" '
.canonical_path == $canonical and
.cli_path == $alias_path and
.is_alias == true
' "$tmp/alias.json" >/dev/null; then
printf 'built dws schema alias view fields are invalid: %s\n' "$alias_path" >&2
exit 1
fi
fi
printf '%s\n' 'schema binary smoke: ok'