Skip to content

feat(schema): split release Catalog into per-product shards to stop main-branch conflicts#656

Closed
typefield wants to merge 2 commits into
DingTalk-Real-AI:mainfrom
typefield:feat/schema-catalog-per-product
Closed

feat(schema): split release Catalog into per-product shards to stop main-branch conflicts#656
typefield wants to merge 2 commits into
DingTalk-Real-AI:mainfrom
typefield:feat/schema-catalog-per-product

Conversation

@typefield

@typefield typefield commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • What changed: The release Schema Catalog is now committed as a per-product split — internal/cli/schema_catalog/catalog.json (global envelope, ~1 MB) plus one internal/cli/schema_catalog/tools/<product>.json shard per product (22 shards) — instead of a single 17.6 MB / 435k-line internal/cli/schema_catalog.json. The loader embeds the directory and reassembles the same SchemaCatalogSnapshot; all generation-time gates, source_hash integrity, and policy checks are unchanged. Also folds in a one-line staticcheck ST1005 fix in an unrelated file so make lint goes fully green.
  • Why this is needed: Every feature PR that touched any command had to regenerate and commit the whole 17.6 MB file, so two concurrent product PRs always collided on an unmergeable blob (+ a wholesale-changing aggregate source_hash). A PR touching one product now rewrites only that product's shard.

Conflict surface:

Before After
aitable-only PR rewrites all 17.6 MB / 435k lines tools/aitable.json (3.2 MB) + catalog.json (1 MB)
calendar-only PR (concurrent) full-file collision tools/calendar.json (654 KB) + catalog.json — disjoint shard, mergeable

Why it's safe (storage-only): source_hash is computed over Version+SurfaceHash+Catalog+Tools, independent of JSON partitioning, so any missing/stale/tampered shard still fails the content-hash check exactly as before. The split happens only at the file-write step; BuildSchemaCatalogSnapshot, ValidateSchemaDeliveryInvariants, and loadSchemaCatalogSnapshot are untouched. Policy jq queries keep their single-document interface via a new scripts/policy/with-catalog.sh reassembly helper.

Verification

  • make build
  • make lint — gofmt + go vet + staticcheck all pass (includes the folded ST1005 fix in agentmetadata/metadata.go)
  • make test — ALL PASSED (cli, contract, integration/extensions, scripts; 0 failed)
  • make policy — ok (22 products, 564 tools; incl. command-registry, command-surface, generated-drift, schema-catalog, schema-binary, agent-examples dry-run)
  • ./scripts/policy/check-generated-drift.sh — deterministic (two runs byte-identical) + matches committed
  • ./scripts/policy/check-command-surface.sh --strict — N/A (no command path/flag/visibility change in this PR)

Notes

  • Scope of the split: only the 12.7 MB tools map is partitioned per product. The 1 MB global catalog.json still gets touched by every product PR because it holds irreducible cross-product aggregates (agent_metadata, interface_metadata, totals). It is small and its diff is localized + mergeable — the unmergeable monster is eliminated. Fully partitioning the global aggregates would require recomputing them at load time (duplicating generation logic) and is deliberately out of scope.
  • Folded lint fix: agentmetadata/metadata.go:372 error string lowercased (the only staticcheck finding on main); CI does not enforce staticcheck today, but this keeps local make lint green. Kept as a separate commit on this branch for review clarity.
  • Files: generator (cmd_schema_catalog/main.go) writes shards; loader (schema_catalog.go) embeds+reassembles; new scripts/policy/with-catalog.sh; 4 policy scripts repointed; Makefile + //go:generate → directory output; tests adapted; AGENTS.md, docs/schema-dynamic-endpoint-design.md, CHANGELOG.md synced; deletes old schema_catalog.json.

The release Catalog was committed as a single 17.6 MB / 435k-line
schema_catalog.json. Any feature PR that touched a command had to
regenerate and commit the whole file, so two concurrent product PRs
always collided on an unmergeable blob.

Split the committed layout into a per-product tree:

  internal/cli/schema_catalog/catalog.json        global envelope + Catalog map (~1 MB)
  internal/cli/schema_catalog/tools/<product>.json   that product's leaf ToolSpecs

A PR that touches a single product now only rewrites that product's
shard plus the small global catalog.json, instead of the whole 12.7 MB
Tools map. The global catalog.json still holds the irreducible
cross-product aggregates (agent_metadata, interface_metadata, totals)
and stays mergeable.

The split is a storage concern only:
- The loader embeds the directory and reassembles the exact same
  SchemaCatalogSnapshot, then runs the existing loadSchemaCatalogSnapshot
  validation unchanged.
- source_hash is computed over Version+SurfaceHash+Catalog+Tools, so it
  is independent of how the JSON is partitioned; any missing, stale, or
  tampered shard still fails the content-hash check exactly as before.

Policy jq queries keep their single-document interface via a new
scripts/policy/with-catalog.sh helper that merges the shards back into
one document. The drift check compares directory trees (diff -qr) and
the go:generate / Makefile targets point at the new directory.

Verified: go build ./..., full cli/generator/outputguard suites,
generated-drift, schema-catalog policy, and the schema binary smoke
check (built dws schema list/--all/<canonical> vs embedded catalog).
staticcheck flags one pre-existing ST1005 finding on main:

  internal/generator/agentmetadata/metadata.go:372
  fmt.Errorf("Agent hint directory is required")
  -> fmt.Errorf("agent hint directory is required")

Go error strings must not be capitalized. Updates the one test that
asserts on this message text. No behavior change.

Verified: make lint (gofmt + go vet + staticcheck) passes.
typefield pushed a commit to typefield/dingtalk-workspace-cli that referenced this pull request Jul 18, 2026
…l-AI#656 merge + fix structure test

After merging PR DingTalk-Real-AI#656 (split schema_catalog.json into per-product shards):
- Regenerated all catalog shards via `make generate-schema` to match the current
  cobra tree (devapp LeafSpec migration changes). The old shards were from DingTalk-Real-AI#656's
  branch (older main), causing source_hash mismatch → embedded catalog load failure.
- Fixed schema_catalog_structure_test.go: reference to deleted `embeddedSchemaCatalogJSON`
  replaced with `json.Marshal(embeddedSchemaCatalog().Snapshot)` — validates the
  reassembled snapshot from the new per-product shard embed.

Verified: all safety tests pass, TestEmbeddedSchemaCatalogStructure passes, drift ok,
make build succeeds.
typefield pushed a commit to typefield/dingtalk-workspace-cli that referenced this pull request Jul 18, 2026
…umption separation

== LeafSpec command framework (internal/helpers/leaf.go) ==
Declarative command construction: LeafSpec/LeafFlag/NewLeafCommand with
Call (pluggable dispatch), LeafInt, PostMount (hook), Trim, Validate.
Collapses per-command hand-written required validation, alias/env fallback,
value transform, 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 test harness asserts toolArgs for all migrated commands.
4 complex commands (delete/robot submit/result/config) kept hand-written
with documented rationale.

== Schema generation/consumption separation ==
- gen.go: isolated //go:generate pragmas from business code (single entry).
- command_meta.go: ResolveMeta(cliPath) → CommandMeta{Identity,Safety,Selection}
  unified consumption API. All consumers (help/schema/agent) call one function.
- command_safety.go: SafetyForCLIPath + RenderSafetyAnnotation — safety
  metadata flows from embedded catalog into --help output.
- calendar.go HelpFunc fix: delegates to root's HelpFunc at help-time
  (not stale capture) so Safety annotations reach calendar commands.
- schema_catalog_structure.go: closed catalog structure validation gate.

== MCP metadata refresh tool ==
cmd/fetch_mcp_metadata: iterates 26 MCP server endpoints via tools/list,
merges with previous data for cross-server interface_ref, fills registry
stubs. make fetch-mcp-metadata target. Utility for future baseline refresh.

== PR DingTalk-Real-AI#656 merge (per-product catalog shards) ==
schema_catalog.json (single file) → schema_catalog/ (catalog.json +
tools/<product>.json shards). Reduces merge conflicts on concurrent PRs.

== AGENTS.md ==
Documents the generation/consumption split: gen.go (entry), 6 inputs →
catalog, ResolveMeta (consumption), fetch-mcp-metadata (refresh).

Verified: make policy exit 0, drift zero, all tests pass.
@PeterGuy326

Copy link
Copy Markdown
Collaborator

Superseded by #676, which carries forward the Catalog sharding work and integrates it with the broader Schema generation, consumption, and registry changes. Closing this duplicate line of development to keep #676 as the canonical follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants