Skip to content

refactor: converge command pipelines onto a typed metadata model + catalog#1191

Open
liangshuo-1 wants to merge 2 commits into
mainfrom
feat/typed-meta-model
Open

refactor: converge command pipelines onto a typed metadata model + catalog#1191
liangshuo-1 wants to merge 2 commits into
mainfrom
feat/typed-meta-model

Conversation

@liangshuo-1
Copy link
Copy Markdown
Collaborator

@liangshuo-1 liangshuo-1 commented May 31, 2026

Operation-runtime convergence (PR1).

Refactor

  • New internal/meta — typed model over meta_data.json; owns field normalization + access-token↔identity (3 duplicate token interpreters merged into one).
  • New internal/apicatalog — navigation (Resolve/MethodRefs/WalkMethods/ServiceMethods/Complete); path resolution moved out of internal/schema.
  • internal/schema is render-only; risk constants moved to internal/core.
  • internal/registry keeps source + scope policy and walks the catalog; cmd/service/cmd/schema thinned to adapters, param flags extracted to paramflags.go/flaggroups.go.

Added

  • Typed flags for raw API commands.
  • Grouped --help with inline enum value=meaning + method affordance.
  • Envelope enumDescriptions.
  • schema tolerates --format/--json (hidden, ignored) for agent compatibility — always emits the JSON envelope.

Fixed

  • Tolerant Option.Value (one bad value can't blank the catalog) + numeric enum sort.
  • Nested-resource completion; auth-hint nested path resolution.

Removed

  • Byte-for-byte envelope golden.

Summary by CodeRabbit

  • New Features

    • Method help now includes affordance guidance (when to use, avoid, prerequisites, examples, related).
    • Service methods expose typed, kebab-case parameter flags with grouped, clearer help and improved shell completion.
    • Schema command accepts normalized path args and consistently emits JSON schema envelopes.
  • Bug Fixes

    • Authorization error hints now show accurate scopes from the live API catalog.
    • Compatibility: legacy --format/--json flags are accepted for compat.
  • Refactor

    • Registry and schema tooling migrated to a typed, catalog-driven model for deterministic behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a29b78b-7d4d-4bcb-a048-1a4ffa1cc708

📥 Commits

Reviewing files that changed from the base of the PR and between 1739a1d and 55eb64a.

📒 Files selected for processing (4)
  • cmd/api/api_test.go
  • cmd/service/paramflags_test.go
  • internal/cmdutil/json.go
  • internal/cmdutil/json_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/service/paramflags_test.go

📝 Walkthrough

Walkthrough

Replace untyped map-based API metadata with typed internal/meta models and add internal/apicatalog; migrate registry loaders/caching, schema envelope assembly, service CLI wiring (flags/help/affordance), scope resolution/completion, and tests to the typed/catalog-driven flow.

Changes

Typed Metadata & Catalog

Layer / File(s) Summary
Meta model, normalization, affordance, identity
internal/meta/*
Adds typed Service, Method, Field, Option; coercion/enum normalization; Affordance parsing; identity/token helpers and tests.
Catalog & method refs
internal/apicatalog/*
New deterministic Catalog with Resolve, Target, MethodRef/ResourceRef, ParsePath, completion (dotted/space), ResolveError, and contract tests.

Registry & caching

Layer / File(s) Summary
Registry adapters & caches
internal/registry/*
Typed embedded/merged loaders, EmbeddedServicesTyped(), ServicesTyped(), ServiceTyped(), and EmbeddedCatalog/RuntimeCatalog constructors; remote cache/overlay updated to typed meta.Service.

Schema assembler & types

Layer / File(s) Summary
Schema assembler, types, lint
internal/schema/*
Replace map-based assembly with Convert(meta.Field), EnvelopeOf/Envelopes(apicatalog.MethodRef), add enumDescriptions, OrderedProps.Set, lint L2 rule for enumDescriptions length, and update tests to typed envelopes.

Service CLI

Layer / File(s) Summary
Service command registration & runtime wiring
cmd/service/service.go, cmd/service/*_test.go
Register commands from registry.RuntimeCatalog().Services(), use meta.Service/meta.Method for NewCmdServiceMethod, typed identity/scope/risk checks, typed request building.
Param flags, grouped help, affordance
cmd/service/paramflags.go, cmd/service/flaggroups.go, cmd/service/affordance.go, cmd/service/*_test.go
Typed param-flag binder with overlay into --params, enum completion and sanitized inline help, grouped flag rendering, affordance rendering, and tests for flag generation, precedence, and help output.
File-upload & identity/risk helpers
internal/cmdutil/*, internal/core/risk.go
Removed some untyped helpers (e.g., DetectFileFields, AccessTokensToIdentities); re-export risk constants from internal/core.

Schema CLI & error hints

Layer / File(s) Summary
Schema CLI & completion
cmd/schema/*
SchemaOptions.Args []string, use apicatalog.ParsePath, route completion via EmbeddedCatalog().Complete, resolve via EmbeddedCatalog().Resolve, print JSON envelopes, and map resolve errors to validation hints.
Auth hint resolution
cmd/error_auth_hint.go, cmd/command_catalog_path_test.go
resolveDeclaredServiceMethodScopes reconstructs catalog path from Cobra ancestry, resolves via registry.RuntimeCatalog().Resolve, and returns scopes when the resolved target is a method; tests added for ancestry->catalog path mapping.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • larksuite/cli#776: Related changes to auth hint enrichment and missing-scope hint logic.
  • larksuite/cli#280: Overlaps in registry test setup and isolation handling for registry globals.
  • larksuite/cli#984: Related auth-hint/refactor work for typed auth errors.

Suggested labels

domain/base, domain/calendar, domain/ccm, domain/mail

"🐰 I hopped through maps and structs today,
Catalogs aligned the API way,
Typed fields bloom where chaos lay,
Help text, flags, and tests all play,
A cheerful CLI—hooray hooray!"

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/typed-meta-model

@github-actions github-actions Bot added the size/XL Architecture-level or global-impact change label May 31, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 31, 2026

Codecov Report

❌ Patch coverage is 86.80490% with 140 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.60%. Comparing base (99e314f) to head (55eb64a).

Files with missing lines Patch % Lines
internal/apicatalog/catalog.go 84.73% 27 Missing and 2 partials ⚠️
internal/meta/meta.go 63.23% 20 Missing and 5 partials ⚠️
cmd/schema/schema.go 50.00% 19 Missing and 2 partials ⚠️
internal/meta/normalize.go 88.23% 12 Missing ⚠️
internal/registry/scopes.go 83.33% 7 Missing and 5 partials ⚠️
cmd/service/service.go 92.30% 8 Missing and 2 partials ⚠️
internal/registry/helpers.go 33.33% 4 Missing and 2 partials ⚠️
internal/registry/remote.go 72.72% 4 Missing and 2 partials ⚠️
cmd/service/paramflags.go 93.05% 3 Missing and 2 partials ⚠️
cmd/error_auth_hint.go 72.72% 0 Missing and 3 partials ⚠️
... and 5 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1191      +/-   ##
==========================================
+ Coverage   69.14%   69.60%   +0.46%     
==========================================
  Files         630      641      +11     
  Lines       59304    58914     -390     
==========================================
+ Hits        41005    41009       +4     
+ Misses      14988    14675     -313     
+ Partials     3311     3230      -81     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 31, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@55eb64a6571ee9bad9c39e2e606f688d40ad2919

🧩 Skill update

npx skills add larksuite/cli#feat/typed-meta-model -y -g

@liangshuo-1 liangshuo-1 force-pushed the feat/typed-meta-model branch from a596d0f to bc06233 Compare May 31, 2026 17:23
@liangshuo-1 liangshuo-1 changed the title feat: typed metadata model + navigation catalog (operation-runtime PR1) refactor: converge command pipelines onto a typed metadata model + catalog May 31, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
internal/registry/scopes.go (1)

126-154: 💤 Low value

Potential data race on cachedAllScopes map.

The cache initialization and access pattern is not thread-safe. If CollectAllScopesFromMeta is called concurrently from multiple goroutines, the nil check, map creation, and writes can race. While CLI commands typically run single-threaded, this could cause issues if the function is used in concurrent contexts (e.g., tests with t.Parallel()).

Consider using sync.Once or sync.Map for thread-safe caching, similar to how embeddedParseOnce is used in loader.go.

♻️ Proposed fix using sync.Once per identity
 var cachedAllScopes map[string][]string
+var cachedAllScopesMu sync.Mutex

 // CollectAllScopesFromMeta collects all unique scopes from from_meta/*.json
 // for the given identity ("user" or "tenant"). Results are deduplicated and sorted.
 func CollectAllScopesFromMeta(identity string) []string {
+	cachedAllScopesMu.Lock()
 	if cachedAllScopes == nil {
 		cachedAllScopes = make(map[string][]string)
 	}
 	if cached, ok := cachedAllScopes[identity]; ok {
+		cachedAllScopesMu.Unlock()
 		return cached
 	}
+	cachedAllScopesMu.Unlock()

 	wantToken := meta.TokenForIdentity(identity)
 	supported := func(m meta.Method) bool { return m.SupportsToken(wantToken) }
 	scopeSet := make(map[string]bool)
 	for _, ref := range RuntimeCatalog().WalkMethods(supported) {
 		for _, s := range ref.Method.Scopes {
 			scopeSet[s] = true
 		}
 	}

 	result := make([]string, 0, len(scopeSet))
 	for s := range scopeSet {
 		result = append(result, s)
 	}
 	sort.Strings(result)
+	cachedAllScopesMu.Lock()
 	cachedAllScopes[identity] = result
+	cachedAllScopesMu.Unlock()
 	return result
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/registry/scopes.go` around lines 126 - 154, CollectAllScopesFromMeta
uses the package-level map cachedAllScopes without synchronization which can
race under concurrent calls; protect access by adding a package-level mutex
(e.g., cachedAllScopesMu sync.RWMutex) and wrap reads/writes: use RLock/RUnlock
to check for an existing cached value, and if not present upgrade to Lock/Unlock
to create the map if nil, compute the scopes, and assign
cachedAllScopes[identity] = result; ensure the double-check pattern (check under
RLock, recheck under Lock) so you don't recompute concurrently. Reference:
CollectAllScopesFromMeta and cachedAllScopes.
cmd/schema/schema.go (1)

106-130: ⚡ Quick win

Clarify resolveError fall-through reachability in cmd/schema/schema.go

The raw return err fall-throughs are currently unreachable: internal/apicatalog.Catalog.Resolve returns only *apicatalog.ResolveError on failure, and its ResolveError.Kind is limited to apicatalog.ErrService, apicatalog.ErrResource, apicatalog.ErrMethod, and apicatalog.ErrPath, all of which are handled.
Optional: add a default case (or wrap the fall-throughs) to preserve the stderr JSON contract if new kinds/extra error types are introduced later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/schema/schema.go` around lines 106 - 130, The resolveError function
currently switches on apicatalog.ResolveError.Kind but handles all known kinds,
making the final return err unreachable; add a default switch case (or an else
after errors.As) that returns the original err (or wraps it consistently via
output.ErrWithHint/output.ExitValidation) to preserve the stderr JSON contract
if new ResolveError kinds or other error types appear later—update the switch in
resolveError (and the errors.As branch) to include a default branch that returns
the unmodified error or a consistent error wrapper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/registry/remote.go`:
- Around line 41-44: The cache round-trip for meta_data.json can drop unknown
JSON keys because MergedRegistry uses typed meta.Service/meta.Method/meta.Field
which only declare explicit json fields; to fix, preserve unknown keys by either
adding a catch-all raw storage field to those types (e.g., an Extra
map[string]json.RawMessage or json.RawMessage wrapper) or by storing and reusing
the original raw JSON for each service when creating MergedRegistry; update the
definitions in meta.Service/meta.Method/meta.Field (or change MergedRegistry to
carry raw payloads) so the cache write emits the original keys unchanged and use
those raw values when marshalling the cached meta_data.json.

---

Nitpick comments:
In `@cmd/schema/schema.go`:
- Around line 106-130: The resolveError function currently switches on
apicatalog.ResolveError.Kind but handles all known kinds, making the final
return err unreachable; add a default switch case (or an else after errors.As)
that returns the original err (or wraps it consistently via
output.ErrWithHint/output.ExitValidation) to preserve the stderr JSON contract
if new ResolveError kinds or other error types appear later—update the switch in
resolveError (and the errors.As branch) to include a default branch that returns
the unmodified error or a consistent error wrapper.

In `@internal/registry/scopes.go`:
- Around line 126-154: CollectAllScopesFromMeta uses the package-level map
cachedAllScopes without synchronization which can race under concurrent calls;
protect access by adding a package-level mutex (e.g., cachedAllScopesMu
sync.RWMutex) and wrap reads/writes: use RLock/RUnlock to check for an existing
cached value, and if not present upgrade to Lock/Unlock to create the map if
nil, compute the scopes, and assign cachedAllScopes[identity] = result; ensure
the double-check pattern (check under RLock, recheck under Lock) so you don't
recompute concurrently. Reference: CollectAllScopesFromMeta and cachedAllScopes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 320d51ee-3b09-4905-8c0d-bfe2ae6d1bf6

📥 Commits

Reviewing files that changed from the base of the PR and between 99e314f and a596d0f.

📒 Files selected for processing (50)
  • cmd/auth/login_interactive.go
  • cmd/command_catalog_path_test.go
  • cmd/error_auth_hint.go
  • cmd/root.go
  • cmd/schema/schema.go
  • cmd/schema/schema_test.go
  • cmd/service/affordance.go
  • cmd/service/affordance_test.go
  • cmd/service/flaggroups.go
  • cmd/service/flaggroups_test.go
  • cmd/service/paramflags.go
  • cmd/service/paramflags_test.go
  • cmd/service/sanitize_test.go
  • cmd/service/service.go
  • cmd/service/service_risk_test.go
  • cmd/service/service_test.go
  • internal/apicatalog/catalog.go
  • internal/apicatalog/catalog_test.go
  • internal/apicatalog/methodref.go
  • internal/apicatalog/path.go
  • internal/apicatalog/path_test.go
  • internal/apicatalog/resolveerror.go
  • internal/cmdutil/fileupload.go
  • internal/cmdutil/identity.go
  • internal/cmdutil/identity_test.go
  • internal/cmdutil/risk.go
  • internal/core/risk.go
  • internal/meta/affordance.go
  • internal/meta/affordance_test.go
  • internal/meta/identity.go
  • internal/meta/identity_test.go
  • internal/meta/meta.go
  • internal/meta/meta_test.go
  • internal/meta/normalize.go
  • internal/meta/normalize_test.go
  • internal/registry/catalog.go
  • internal/registry/helpers.go
  • internal/registry/loader.go
  • internal/registry/registry_test.go
  • internal/registry/remote.go
  • internal/registry/remote_test.go
  • internal/registry/scope_hint.go
  • internal/registry/scopes.go
  • internal/schema/assembler.go
  • internal/schema/assembler_test.go
  • internal/schema/lint.go
  • internal/schema/lint_test.go
  • internal/schema/path.go
  • internal/schema/types.go
  • internal/schema/types_test.go
💤 Files with no reviewable changes (4)
  • internal/schema/path.go
  • internal/cmdutil/identity_test.go
  • internal/cmdutil/identity.go
  • internal/cmdutil/fileupload.go

Comment thread internal/registry/remote.go
@liangshuo-1 liangshuo-1 force-pushed the feat/typed-meta-model branch 2 times, most recently from 830b07c to 7af9820 Compare May 31, 2026 17:37
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
internal/registry/remote.go (1)

38-44: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Preserve unknown remote metadata fields in the cache.

This now decodes services into []meta.Service and then re-marshals envelope.Data before writing remote_meta.json. Any fields the typed model does not declare are lost on that round-trip, so the cache can silently diverge from the server payload and hide newly shipped metadata until the binary is updated. Please cache the raw data JSON or carry unknown fields through the typed model before writing.

Also applies to: 219-225

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/registry/remote.go` around lines 38 - 44, The cache currently
decodes envelope.Data into []meta.Service (type MergedRegistry with Services
[]meta.Service) which drops unknown fields when re-marshaling remote_meta.json;
preserve unknown remote metadata by either storing the raw JSON (e.g. keep
envelope.Data as a json.RawMessage and write that into remote_meta.json) or by
changing the MergedRegistry.Services field to hold the raw service objects (e.g.
[]json.RawMessage or []map[string]interface{}) and only decode into meta.Service
where typed access is needed; update the code paths that build/write
remote_meta.json (the code handling envelope.Data and MergedRegistry) to write
the preserved raw JSON instead of the re-marshaled typed struct so unknown
fields are kept.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/meta/affordance_test.go`:
- Around line 13-25: The test currently uses an empty json.RawMessage for the
"nil" case but doesn't exercise a true nil payload; update the test to add a
separate case that passes a nil Affordance (e.g., Method{Affordance:
json.RawMessage(nil)} or a zero-value Affordance) and assert ParsedAffordance()
returns ok=false, while leaving the existing `""` entry to continue testing an
empty payload; reference the Method type and its ParsedAffordance() method when
making the change.

---

Duplicate comments:
In `@internal/registry/remote.go`:
- Around line 38-44: The cache currently decodes envelope.Data into
[]meta.Service (type MergedRegistry with Services []meta.Service) which drops
unknown fields when re-marshaling remote_meta.json; preserve unknown remote
metadata by either storing the raw JSON (e.g. keep envelope.Data as a
json.RawMessage and write that into remote_meta.json) or by changing the
MergedRegistry.Services field to hold the raw service objects (e.g.
[]json.RawMessage or []map[string]interface{}) and only decode into meta.Service
where typed access is needed; update the code paths that build/write
remote_meta.json (the code handling envelope.Data and MergedRegistry) to write
the preserved raw JSON instead of the re-marshaled typed struct so unknown
fields are kept.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 54ab893f-ac27-4eef-b4eb-67737c9d44f8

📥 Commits

Reviewing files that changed from the base of the PR and between a596d0f and 7af9820.

📒 Files selected for processing (50)
  • cmd/auth/login_interactive.go
  • cmd/command_catalog_path_test.go
  • cmd/error_auth_hint.go
  • cmd/root.go
  • cmd/schema/schema.go
  • cmd/schema/schema_test.go
  • cmd/service/affordance.go
  • cmd/service/affordance_test.go
  • cmd/service/flaggroups.go
  • cmd/service/flaggroups_test.go
  • cmd/service/paramflags.go
  • cmd/service/paramflags_test.go
  • cmd/service/sanitize_test.go
  • cmd/service/service.go
  • cmd/service/service_risk_test.go
  • cmd/service/service_test.go
  • internal/apicatalog/catalog.go
  • internal/apicatalog/catalog_test.go
  • internal/apicatalog/methodref.go
  • internal/apicatalog/path.go
  • internal/apicatalog/path_test.go
  • internal/apicatalog/resolveerror.go
  • internal/cmdutil/fileupload.go
  • internal/cmdutil/identity.go
  • internal/cmdutil/identity_test.go
  • internal/cmdutil/risk.go
  • internal/core/risk.go
  • internal/meta/affordance.go
  • internal/meta/affordance_test.go
  • internal/meta/identity.go
  • internal/meta/identity_test.go
  • internal/meta/meta.go
  • internal/meta/meta_test.go
  • internal/meta/normalize.go
  • internal/meta/normalize_test.go
  • internal/registry/catalog.go
  • internal/registry/helpers.go
  • internal/registry/loader.go
  • internal/registry/registry_test.go
  • internal/registry/remote.go
  • internal/registry/remote_test.go
  • internal/registry/scope_hint.go
  • internal/registry/scopes.go
  • internal/schema/assembler.go
  • internal/schema/assembler_test.go
  • internal/schema/lint.go
  • internal/schema/lint_test.go
  • internal/schema/path.go
  • internal/schema/types.go
  • internal/schema/types_test.go
💤 Files with no reviewable changes (4)
  • internal/schema/path.go
  • internal/cmdutil/identity.go
  • internal/cmdutil/identity_test.go
  • internal/cmdutil/fileupload.go
🚧 Files skipped from review as they are similar to previous changes (35)
  • internal/core/risk.go
  • internal/schema/types_test.go
  • cmd/service/affordance_test.go
  • internal/cmdutil/risk.go
  • internal/meta/affordance.go
  • internal/apicatalog/resolveerror.go
  • cmd/service/sanitize_test.go
  • internal/apicatalog/methodref.go
  • cmd/service/service_risk_test.go
  • cmd/auth/login_interactive.go
  • cmd/service/affordance.go
  • internal/apicatalog/path.go
  • cmd/command_catalog_path_test.go
  • internal/meta/normalize_test.go
  • cmd/service/flaggroups_test.go
  • internal/apicatalog/path_test.go
  • internal/registry/helpers.go
  • internal/registry/remote_test.go
  • internal/meta/identity_test.go
  • internal/schema/lint.go
  • internal/schema/assembler.go
  • cmd/service/paramflags.go
  • cmd/service/flaggroups.go
  • internal/registry/registry_test.go
  • internal/registry/scopes.go
  • internal/apicatalog/catalog_test.go
  • internal/meta/meta.go
  • internal/schema/lint_test.go
  • internal/meta/normalize.go
  • cmd/error_auth_hint.go
  • internal/registry/loader.go
  • cmd/schema/schema_test.go
  • cmd/service/paramflags_test.go
  • cmd/service/service.go
  • internal/schema/assembler_test.go

Comment on lines +13 to +25
notOK := map[string]string{
"nil": ``,
"empty object": `{}`,
"all empty arrays": `{"use_when":[],"do_not_use_when":[],"prerequisites":[],"examples":[],"related":[]}`,
"malformed string": `"not an object"`,
"malformed number": `42`,
"nested type mismatch": `{"examples":"should be a list"}`,
}
for name, raw := range notOK {
t.Run(name, func(t *testing.T) {
if _, ok := (Method{Affordance: json.RawMessage(raw)}).ParsedAffordance(); ok {
t.Errorf("ParsedAffordance(%s) ok=true, want false", raw)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Actually cover a nil affordance payload.

The "nil" case currently passes an empty json.RawMessage, not a nil one, so a regression in the nil-input branch would still pass. Add a separate zero-value / nil Affordance case and keep "" as the empty-payload case.

Suggested test adjustment
 func TestMethod_ParsedAffordance(t *testing.T) {
 	// absent / empty / malformed all resolve to ok=false.
+	t.Run("nil", func(t *testing.T) {
+		if _, ok := (Method{}).ParsedAffordance(); ok {
+			t.Error("ParsedAffordance(nil) ok=true, want false")
+		}
+	})
+
 	notOK := map[string]string{
-		"nil":                  ``,
+		"empty payload":        ``,
 		"empty object":         `{}`,
 		"all empty arrays":     `{"use_when":[],"do_not_use_when":[],"prerequisites":[],"examples":[],"related":[]}`,
 		"malformed string":     `"not an object"`,
 		"malformed number":     `42`,
 		"nested type mismatch": `{"examples":"should be a list"}`,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/meta/affordance_test.go` around lines 13 - 25, The test currently
uses an empty json.RawMessage for the "nil" case but doesn't exercise a true nil
payload; update the test to add a separate case that passes a nil Affordance
(e.g., Method{Affordance: json.RawMessage(nil)} or a zero-value Affordance) and
assert ParsedAffordance() returns ok=false, while leaving the existing `""`
entry to continue testing an empty payload; reference the Method type and its
ParsedAffordance() method when making the change.

@liangshuo-1 liangshuo-1 force-pushed the feat/typed-meta-model branch 2 times, most recently from 5401796 to 40dda0d Compare May 31, 2026 17:51
…talog

- New internal/meta: typed model over meta_data.json; owns field
  normalization + the access-token<->identity bijection (3 duplicate token
  interpreters merged into one).
- New internal/apicatalog: navigation (Resolve/MethodRefs/WalkMethods/
  ServiceMethods/Complete); path resolution moved out of internal/schema.
- internal/schema is render-only; risk constants moved to internal/core.
- internal/registry keeps source + scope policy and walks the catalog;
  cmd/service param flags extracted to paramflags.go/flaggroups.go.
- Add: typed flags for raw API commands; grouped --help with inline enum
  value=meaning + method affordance; envelope enumDescriptions; schema
  tolerates --format/--json for agent compatibility (always emits the
  JSON envelope).
- Fix: tolerant Option.Value so one bad value can't blank the catalog;
  nested-resource completion; auth-hint nested path.
- Remove: byte-for-byte envelope golden.

Change-Id: Ia7b870b3d4c6f2d56b9dc3dcdc91f2fdaeaddae4
@liangshuo-1 liangshuo-1 force-pushed the feat/typed-meta-model branch from 40dda0d to 1739a1d Compare May 31, 2026 17:53
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/meta/meta.go (1)

195-215: ⚡ Quick win

Consider panicking on marshal/unmarshal errors in test helpers.

FromMap and ServiceFromMap silently return zero values when JSON encoding fails. Since these are test-only helpers (per the comments), silent failures can hide malformed test fixtures.

Consider:

 func FromMap(method map[string]interface{}) Method {
 	b, err := json.Marshal(method)
 	if err != nil {
-		return Method{}
+		panic(fmt.Sprintf("FromMap: marshal failed: %v", err))
 	}
 	var m Method
-	_ = json.Unmarshal(b, &m)
+	if err := json.Unmarshal(b, &m); err != nil {
+		panic(fmt.Sprintf("FromMap: unmarshal failed: %v", err))
+	}
 	return m
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/meta/meta.go` around lines 195 - 215, The FromMap and ServiceFromMap
test helpers currently swallow json.Marshal/json.Unmarshal errors and return
zero values; change them to fail fast by panicking on marshal or unmarshal
errors so malformed test fixtures are obvious: in FromMap (function FromMap,
type Method) and ServiceFromMap (function ServiceFromMap, type Service) check
errs from json.Marshal and json.Unmarshal and call panic(fmt.Errorf(...)) with
the error (or use panic(err)) instead of returning an empty struct.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/service/paramflags.go`:
- Around line 72-81: In paramFlagBinder.overlay, guard against params being nil
to avoid a panic when params was parsed from a JSON "null": check params at the
start of overlay (or before writing) and either return early or allocate a new
map[string]interface{} so that assignments like params[pf.field.Name] =
pf.read() are safe; update the function containing overlay
(paramFlagBinder.overlay) to perform this nil-check/initialization before the
loop that uses pf.field.Name and pf.read().

---

Nitpick comments:
In `@internal/meta/meta.go`:
- Around line 195-215: The FromMap and ServiceFromMap test helpers currently
swallow json.Marshal/json.Unmarshal errors and return zero values; change them
to fail fast by panicking on marshal or unmarshal errors so malformed test
fixtures are obvious: in FromMap (function FromMap, type Method) and
ServiceFromMap (function ServiceFromMap, type Service) check errs from
json.Marshal and json.Unmarshal and call panic(fmt.Errorf(...)) with the error
(or use panic(err)) instead of returning an empty struct.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 79fd8003-ccee-4fa1-a39d-0bc311342291

📥 Commits

Reviewing files that changed from the base of the PR and between 7af9820 and 1739a1d.

📒 Files selected for processing (50)
  • cmd/auth/login_interactive.go
  • cmd/command_catalog_path_test.go
  • cmd/error_auth_hint.go
  • cmd/root.go
  • cmd/schema/schema.go
  • cmd/schema/schema_test.go
  • cmd/service/affordance.go
  • cmd/service/affordance_test.go
  • cmd/service/flaggroups.go
  • cmd/service/flaggroups_test.go
  • cmd/service/paramflags.go
  • cmd/service/paramflags_test.go
  • cmd/service/sanitize_test.go
  • cmd/service/service.go
  • cmd/service/service_risk_test.go
  • cmd/service/service_test.go
  • internal/apicatalog/catalog.go
  • internal/apicatalog/catalog_test.go
  • internal/apicatalog/methodref.go
  • internal/apicatalog/path.go
  • internal/apicatalog/path_test.go
  • internal/apicatalog/resolveerror.go
  • internal/cmdutil/fileupload.go
  • internal/cmdutil/identity.go
  • internal/cmdutil/identity_test.go
  • internal/cmdutil/risk.go
  • internal/core/risk.go
  • internal/meta/affordance.go
  • internal/meta/affordance_test.go
  • internal/meta/identity.go
  • internal/meta/identity_test.go
  • internal/meta/meta.go
  • internal/meta/meta_test.go
  • internal/meta/normalize.go
  • internal/meta/normalize_test.go
  • internal/registry/catalog.go
  • internal/registry/helpers.go
  • internal/registry/loader.go
  • internal/registry/registry_test.go
  • internal/registry/remote.go
  • internal/registry/remote_test.go
  • internal/registry/scope_hint.go
  • internal/registry/scopes.go
  • internal/schema/assembler.go
  • internal/schema/assembler_test.go
  • internal/schema/lint.go
  • internal/schema/lint_test.go
  • internal/schema/path.go
  • internal/schema/types.go
  • internal/schema/types_test.go
💤 Files with no reviewable changes (4)
  • internal/cmdutil/identity.go
  • internal/cmdutil/identity_test.go
  • internal/schema/path.go
  • internal/cmdutil/fileupload.go
✅ Files skipped from review due to trivial changes (2)
  • cmd/service/service_risk_test.go
  • cmd/root.go
🚧 Files skipped from review as they are similar to previous changes (30)
  • internal/schema/types_test.go
  • internal/meta/affordance_test.go
  • internal/registry/catalog.go
  • internal/schema/lint_test.go
  • internal/core/risk.go
  • internal/meta/affordance.go
  • cmd/auth/login_interactive.go
  • internal/apicatalog/path.go
  • internal/meta/meta_test.go
  • internal/cmdutil/risk.go
  • cmd/service/sanitize_test.go
  • cmd/service/affordance.go
  • cmd/service/affordance_test.go
  • cmd/service/flaggroups_test.go
  • internal/schema/lint.go
  • internal/meta/identity_test.go
  • internal/meta/identity.go
  • internal/meta/normalize_test.go
  • cmd/command_catalog_path_test.go
  • cmd/schema/schema_test.go
  • cmd/error_auth_hint.go
  • internal/meta/normalize.go
  • cmd/service/paramflags_test.go
  • internal/registry/registry_test.go
  • internal/apicatalog/catalog_test.go
  • internal/registry/scopes.go
  • internal/registry/remote_test.go
  • internal/registry/loader.go
  • internal/schema/assembler.go
  • internal/schema/assembler_test.go

Comment thread cmd/service/paramflags.go
The JSON literal `null` unmarshals into a nil map with err==nil, so
ParseJSONMap reported success while handing back an unwritable map. Both
callers then panic on the first overlay write:
  - cmd/service: paramFlagBinder.overlay sets params[field] = flag value
  - cmd/api: params["page_size"] = N
Triggered by e.g. `--params null --chat-id X` or `--params null --page-size N`.

Normalize a nil result to an empty map at the source, so the contract
("returns a writable map on success") holds for every caller and matches
the existing empty-input case. Regression tests pin the contract at the
unit level and at both call sites.

Change-Id: I75e695c039be96acfc24f4fd134a64a2984bd1e7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Architecture-level or global-impact change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant