chore: import upstream snapshot with attribution
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package cmdmeta is the single source of truth for command metadata that the
|
||||
// policy engine, the hook selector, and help rendering consume. It wraps the
|
||||
// existing cmdutil annotations (risk_level, supportedIdentities) and adds the
|
||||
// "domain" axis that the hook selector and Rule path globs need, plus the
|
||||
// affordance ref (service, method id) that lets service-method and shortcut
|
||||
// help share one usage-guidance lookup path.
|
||||
//
|
||||
// Three axes:
|
||||
//
|
||||
// - Domain - business domain ("im", "docs", "contact", ...). Inherited
|
||||
// from the nearest ancestor when not set on the command
|
||||
// itself. Stored on a new annotation key (the cmdutil
|
||||
// risk_level / supportedIdentities keys are left untouched
|
||||
// for backward compatibility).
|
||||
// - Risk - "read" | "write" | "high-risk-write". Inherited like
|
||||
// Domain. Reuses cmdutil.SetRisk / GetRisk under the hood.
|
||||
// - Identities - allowed identity set. Child explicit override semantics:
|
||||
// the first ancestor (including self) with a non-nil set
|
||||
// wins. Reuses cmdutil.SetSupportedIdentities /
|
||||
// GetSupportedIdentities.
|
||||
//
|
||||
// Missing values are returned as the zero value with ok=false (where the
|
||||
// signature exposes it). Interpretation is up to the consumer: the policy
|
||||
// engine treats a missing risk as fail-closed when a Rule is registered
|
||||
// without AllowUnannotated=true, and as allow otherwise. Identities still
|
||||
// defaults to ALLOW. Do not synthesise defaults here -- let each consumer
|
||||
// decide.
|
||||
package cmdmeta
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
)
|
||||
|
||||
// Source identifies how a command entered the repository-owned command tree.
|
||||
type Source string
|
||||
|
||||
const (
|
||||
SourceBuiltin Source = "builtin"
|
||||
SourceShortcut Source = "shortcut"
|
||||
SourceService Source = "service"
|
||||
)
|
||||
|
||||
const (
|
||||
// domainAnnotationKey is the cobra Annotation key for the business domain.
|
||||
// Kept distinct from cmdutil.* keys so this package can evolve without
|
||||
// disturbing existing readers.
|
||||
domainAnnotationKey = "cmdmeta.domain"
|
||||
|
||||
sourceAnnotationKey = "cmdmeta.source"
|
||||
generatedAnnotationKey = "cmdmeta.generated"
|
||||
|
||||
// affordance{Service,Method}Key locate the command's usage-guidance overlay
|
||||
// entry (see internal/affordance). Both service-method commands and
|
||||
// +-prefixed shortcuts set these so help rendering shares one lookup path.
|
||||
affordanceServiceKey = "cmdmeta.affordance.service"
|
||||
affordanceMethodKey = "cmdmeta.affordance.method"
|
||||
)
|
||||
|
||||
// Meta groups the three command-level metadata axes consumed by the policy
|
||||
// engine and hook selectors.
|
||||
type Meta struct {
|
||||
Domain string
|
||||
Risk string
|
||||
Identities []string
|
||||
}
|
||||
|
||||
// Apply writes metadata onto a cobra command. Empty fields are skipped: pass
|
||||
// the value via the underlying cmdutil setter if you need to write an empty
|
||||
// string / empty slice explicitly.
|
||||
func Apply(cmd *cobra.Command, m Meta) {
|
||||
if m.Domain != "" {
|
||||
SetDomain(cmd, m.Domain)
|
||||
}
|
||||
if m.Risk != "" {
|
||||
cmdutil.SetRisk(cmd, m.Risk)
|
||||
}
|
||||
if m.Identities != nil {
|
||||
cmdutil.SetSupportedIdentities(cmd, m.Identities)
|
||||
}
|
||||
}
|
||||
|
||||
// Get resolves the effective metadata for a command, walking up the parent
|
||||
// chain for Domain, Risk, and Identities. All three axes use the same
|
||||
// nearest-ancestor-wins rule.
|
||||
//
|
||||
// Identities note: cmdutil.GetSupportedIdentities collapses both the
|
||||
// "annotation absent" and "annotation set to empty string" cases to nil.
|
||||
// A child cannot therefore express "deny inheritance" with an empty
|
||||
// annotation; the walk simply continues up the parent chain when nil is
|
||||
// returned. To override a parent, the child must set a non-empty slice
|
||||
// (e.g. ["bot"]).
|
||||
func Get(cmd *cobra.Command) Meta {
|
||||
risk, _ := Risk(cmd)
|
||||
return Meta{
|
||||
Domain: Domain(cmd),
|
||||
Risk: risk,
|
||||
Identities: Identities(cmd),
|
||||
}
|
||||
}
|
||||
|
||||
// SetDomain stores the domain annotation on a single command (no
|
||||
// inheritance is performed on write).
|
||||
func SetDomain(cmd *cobra.Command, domain string) {
|
||||
if domain == "" {
|
||||
return
|
||||
}
|
||||
if cmd.Annotations == nil {
|
||||
cmd.Annotations = map[string]string{}
|
||||
}
|
||||
cmd.Annotations[domainAnnotationKey] = domain
|
||||
}
|
||||
|
||||
// SetSource stores the command source on a single command. The generated flag
|
||||
// is written explicitly so child commands can opt out of inherited service
|
||||
// metadata.
|
||||
func SetSource(cmd *cobra.Command, source Source, generated bool) {
|
||||
if source == "" {
|
||||
return
|
||||
}
|
||||
if cmd.Annotations == nil {
|
||||
cmd.Annotations = map[string]string{}
|
||||
}
|
||||
cmd.Annotations[sourceAnnotationKey] = string(source)
|
||||
if generated {
|
||||
cmd.Annotations[generatedAnnotationKey] = "true"
|
||||
} else {
|
||||
cmd.Annotations[generatedAnnotationKey] = "false"
|
||||
}
|
||||
}
|
||||
|
||||
// SetAffordanceRef records which affordance overlay entry (service, method id)
|
||||
// a command maps to, so help rendering can look up its usage guidance. Stored
|
||||
// on the command itself (no inheritance): each method / shortcut owns its ref.
|
||||
// A no-op if either coordinate is empty.
|
||||
func SetAffordanceRef(cmd *cobra.Command, service, method string) {
|
||||
if service == "" || method == "" {
|
||||
return
|
||||
}
|
||||
if cmd.Annotations == nil {
|
||||
cmd.Annotations = map[string]string{}
|
||||
}
|
||||
cmd.Annotations[affordanceServiceKey] = service
|
||||
cmd.Annotations[affordanceMethodKey] = method
|
||||
}
|
||||
|
||||
// AffordanceRef returns the command's own affordance overlay coordinates.
|
||||
// ok is false when the command carries no ref.
|
||||
func AffordanceRef(cmd *cobra.Command) (service, method string, ok bool) {
|
||||
if cmd.Annotations == nil {
|
||||
return "", "", false
|
||||
}
|
||||
service = cmd.Annotations[affordanceServiceKey]
|
||||
method = cmd.Annotations[affordanceMethodKey]
|
||||
if service == "" || method == "" {
|
||||
return "", "", false
|
||||
}
|
||||
return service, method, true
|
||||
}
|
||||
|
||||
// Domain returns the nearest-ancestor domain for the command. Empty string
|
||||
// when no ancestor has the annotation -- this is the "unknown" state the
|
||||
// policy engine must treat as ALLOW.
|
||||
func Domain(cmd *cobra.Command) string {
|
||||
for c := cmd; c != nil; c = c.Parent() {
|
||||
if c.Annotations == nil {
|
||||
continue
|
||||
}
|
||||
if v, ok := c.Annotations[domainAnnotationKey]; ok && v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SourceOf returns the nearest-ancestor command source.
|
||||
func SourceOf(cmd *cobra.Command) (Source, bool) {
|
||||
for c := cmd; c != nil; c = c.Parent() {
|
||||
if c.Annotations == nil {
|
||||
continue
|
||||
}
|
||||
if v := c.Annotations[sourceAnnotationKey]; v != "" {
|
||||
return Source(v), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Generated returns the nearest generated annotation. An explicit false on a
|
||||
// child command stops inheritance from a generated parent.
|
||||
func Generated(cmd *cobra.Command) bool {
|
||||
for c := cmd; c != nil; c = c.Parent() {
|
||||
if c.Annotations == nil {
|
||||
continue
|
||||
}
|
||||
if v, ok := c.Annotations[generatedAnnotationKey]; ok {
|
||||
return v == "true"
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Risk returns the nearest-ancestor risk level (via cmdutil.GetRisk).
|
||||
// ok=false signals "unknown" -- the policy engine treats this as
|
||||
// fail-closed (deny with risk_not_annotated) whenever a Rule without
|
||||
// AllowUnannotated=true is active, and as allow otherwise.
|
||||
func Risk(cmd *cobra.Command) (level string, ok bool) {
|
||||
for c := cmd; c != nil; c = c.Parent() {
|
||||
if level, ok = cmdutil.GetRisk(c); ok {
|
||||
return level, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Identities returns the first non-nil identity set found while walking up
|
||||
// the parent chain. nil signals "unknown" -- the policy engine treats this
|
||||
// as ALLOW.
|
||||
//
|
||||
// cmdutil.GetSupportedIdentities returns nil when the annotation is absent
|
||||
// or empty; an explicit non-empty set (even ["user"] alone) stops the walk.
|
||||
func Identities(cmd *cobra.Command) []string {
|
||||
for c := cmd; c != nil; c = c.Parent() {
|
||||
if ids := cmdutil.GetSupportedIdentities(c); ids != nil {
|
||||
return ids
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmdmeta_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/larksuite/cli/internal/cmdmeta"
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
)
|
||||
|
||||
func TestApply_writesAllFields(t *testing.T) {
|
||||
cmd := &cobra.Command{Use: "fetch"}
|
||||
cmdmeta.Apply(cmd, cmdmeta.Meta{
|
||||
Domain: "docs",
|
||||
Risk: "write",
|
||||
Identities: []string{"user", "bot"},
|
||||
})
|
||||
|
||||
if got := cmdmeta.Domain(cmd); got != "docs" {
|
||||
t.Fatalf("Domain = %q, want %q", got, "docs")
|
||||
}
|
||||
if got, ok := cmdmeta.Risk(cmd); !ok || got != "write" {
|
||||
t.Fatalf("Risk = (%q,%v), want (%q,true)", got, ok, "write")
|
||||
}
|
||||
if got := cmdmeta.Identities(cmd); !reflect.DeepEqual(got, []string{"user", "bot"}) {
|
||||
t.Fatalf("Identities = %v, want [user bot]", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApply_emptyFieldsSkipped(t *testing.T) {
|
||||
cmd := &cobra.Command{Use: "fetch"}
|
||||
cmdmeta.Apply(cmd, cmdmeta.Meta{}) // nothing
|
||||
if got := cmdmeta.Domain(cmd); got != "" {
|
||||
t.Fatalf("Domain expected unset, got %q", got)
|
||||
}
|
||||
if _, ok := cmdmeta.Risk(cmd); ok {
|
||||
t.Fatalf("Risk expected unset")
|
||||
}
|
||||
if got := cmdmeta.Identities(cmd); got != nil {
|
||||
t.Fatalf("Identities expected nil, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Domain inherits from the nearest ancestor; risk and identities behave the
|
||||
// same way. We verify each axis with a 3-level tree:
|
||||
//
|
||||
// root (domain=docs, risk=read, identities=[user])
|
||||
// group
|
||||
// leaf
|
||||
func TestGet_inheritsFromAncestor(t *testing.T) {
|
||||
root := &cobra.Command{Use: "lark-cli"}
|
||||
group := &cobra.Command{Use: "docs"}
|
||||
leaf := &cobra.Command{Use: "fetch"}
|
||||
root.AddCommand(group)
|
||||
group.AddCommand(leaf)
|
||||
|
||||
cmdmeta.Apply(root, cmdmeta.Meta{
|
||||
Domain: "docs",
|
||||
Risk: "read",
|
||||
Identities: []string{"user"},
|
||||
})
|
||||
|
||||
got := cmdmeta.Get(leaf)
|
||||
want := cmdmeta.Meta{
|
||||
Domain: "docs",
|
||||
Risk: "read",
|
||||
Identities: []string{"user"},
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("Get(leaf) = %+v, want %+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Closest ancestor wins -- a mid-level override is preferred over root.
|
||||
func TestGet_nearestAncestorWins(t *testing.T) {
|
||||
root := &cobra.Command{Use: "lark-cli"}
|
||||
group := &cobra.Command{Use: "docs"}
|
||||
leaf := &cobra.Command{Use: "fetch"}
|
||||
root.AddCommand(group)
|
||||
group.AddCommand(leaf)
|
||||
|
||||
cmdmeta.SetDomain(root, "docs")
|
||||
cmdmeta.SetDomain(group, "docs-override")
|
||||
cmdutil.SetRisk(root, "read")
|
||||
cmdutil.SetRisk(group, "high-risk-write")
|
||||
|
||||
if got := cmdmeta.Domain(leaf); got != "docs-override" {
|
||||
t.Fatalf("Domain = %q, want docs-override (nearest)", got)
|
||||
}
|
||||
if got, _ := cmdmeta.Risk(leaf); got != "high-risk-write" {
|
||||
t.Fatalf("Risk = %q, want high-risk-write (nearest)", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown axes return zero / nil so the policy engine can apply the
|
||||
// "unknown => ALLOW" contract.
|
||||
func TestGet_unknownReturnsZero(t *testing.T) {
|
||||
cmd := &cobra.Command{Use: "orphan"}
|
||||
if got := cmdmeta.Domain(cmd); got != "" {
|
||||
t.Fatalf("Domain = %q, want empty for unknown", got)
|
||||
}
|
||||
if level, ok := cmdmeta.Risk(cmd); ok || level != "" {
|
||||
t.Fatalf("Risk = (%q,%v), want empty / false for unknown", level, ok)
|
||||
}
|
||||
if ids := cmdmeta.Identities(cmd); ids != nil {
|
||||
t.Fatalf("Identities = %v, want nil for unknown", ids)
|
||||
}
|
||||
}
|
||||
|
||||
// Child explicitly overriding identities stops the parent walk.
|
||||
func TestIdentities_childOverridesParent(t *testing.T) {
|
||||
parent := &cobra.Command{Use: "docs"}
|
||||
child := &cobra.Command{Use: "preview"}
|
||||
parent.AddCommand(child)
|
||||
|
||||
cmdutil.SetSupportedIdentities(parent, []string{"user", "bot"})
|
||||
cmdutil.SetSupportedIdentities(child, []string{"bot"})
|
||||
|
||||
got := cmdmeta.Identities(child)
|
||||
if !reflect.DeepEqual(got, []string{"bot"}) {
|
||||
t.Fatalf("Identities(child) = %v, want [bot]", got)
|
||||
}
|
||||
}
|
||||
|
||||
// SetDomain with empty value is a no-op (no annotation written, so a
|
||||
// later inherited read still works).
|
||||
func TestSetDomain_emptyIsNoop(t *testing.T) {
|
||||
parent := &cobra.Command{Use: "docs"}
|
||||
cmdmeta.SetDomain(parent, "docs")
|
||||
|
||||
child := &cobra.Command{Use: "fetch"}
|
||||
parent.AddCommand(child)
|
||||
|
||||
cmdmeta.SetDomain(child, "") // no-op
|
||||
if got := cmdmeta.Domain(child); got != "docs" {
|
||||
t.Fatalf("Domain(child) = %q, want inherited 'docs'", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceGenerated_childFalseStopsParentGeneratedInheritance(t *testing.T) {
|
||||
parent := &cobra.Command{Use: "docs"}
|
||||
child := &cobra.Command{Use: "+fetch"}
|
||||
parent.AddCommand(child)
|
||||
|
||||
cmdmeta.SetSource(parent, cmdmeta.SourceService, true)
|
||||
cmdmeta.SetSource(child, cmdmeta.SourceShortcut, false)
|
||||
|
||||
if source, ok := cmdmeta.SourceOf(child); !ok || source != cmdmeta.SourceShortcut {
|
||||
t.Fatalf("SourceOf(child) = (%q,%v), want (shortcut,true)", source, ok)
|
||||
}
|
||||
if cmdmeta.Generated(child) {
|
||||
t.Fatal("Generated(child) = true, want false")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user