mirror of
https://github.com/larksuite/cli.git
synced 2026-07-09 02:14:02 +08:00
refactor(envelope): rename error.type pruning/strict_mode to command_denied
The envelope's `type` field was leaking implementation terms ("pruning",
"strict_mode") that describe enforcement mechanism rather than the user-
facing semantic. It also duplicated `detail.layer`, and forced consumers
to branch on two values for the same conceptual error ("a command was
denied by policy").
Collapse both into a single semantic type "command_denied". The
enforcement layer ("pruning" / "strict_mode") is preserved in
`detail.layer` so debugging and per-layer diagnostics still work.
This commit is contained in:
@@ -50,7 +50,7 @@ For AI agents: this command blocks until the user completes authorization in the
|
||||
browser. Run it in the background and retrieve the verification URL from its output.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if mode := f.ResolveStrictMode(cmd.Context()); mode == core.StrictModeBot {
|
||||
return output.ErrWithHint(output.ExitValidation, "strict_mode",
|
||||
return output.ErrWithHint(output.ExitValidation, "command_denied",
|
||||
fmt.Sprintf("strict mode is %q, user login is disabled in this profile", mode),
|
||||
"if the user explicitly wants to switch to user identity, see `lark-cli config strict-mode --help` (confirm with the user before switching; switching does NOT require re-bind)")
|
||||
}
|
||||
|
||||
@@ -117,8 +117,8 @@ max_risk: write
|
||||
t.Fatalf("+delete-doc RunE should return an error")
|
||||
}
|
||||
var exitErr *output.ExitError
|
||||
if !errors.As(err, &exitErr) || exitErr.Detail == nil || exitErr.Detail.Type != "pruning" {
|
||||
t.Fatalf("expected pruning ExitError, got %T %+v", err, err)
|
||||
if !errors.As(err, &exitErr) || exitErr.Detail == nil || exitErr.Detail.Type != "command_denied" {
|
||||
t.Fatalf("expected command_denied ExitError, got %T %+v", err, err)
|
||||
}
|
||||
detail, ok := exitErr.Detail.Detail.(map[string]any)
|
||||
if !ok || detail["reason_code"] != "command_denylisted" {
|
||||
|
||||
@@ -79,7 +79,7 @@ func strictModeStubFrom(child *cobra.Command, mode core.StrictMode) *cobra.Comma
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return output.ErrWithHint(output.ExitValidation, "strict_mode",
|
||||
return output.ErrWithHint(output.ExitValidation, "command_denied",
|
||||
fmt.Sprintf("strict mode is %q, only %s-identity commands are available", mode, mode.ForcedIdentity()),
|
||||
"if the user explicitly wants to switch policy, see `lark-cli config strict-mode --help` (confirm with the user before switching; switching does NOT require re-bind)")
|
||||
},
|
||||
|
||||
@@ -353,7 +353,7 @@ func TestIntegration_StrictModeBot_ProfileOverride_DirectAuthLoginReturnsEnvelop
|
||||
assertEnvelope(t, code, output.ExitValidation, stdout, stderr, output.ErrorEnvelope{
|
||||
OK: false,
|
||||
Error: &output.ErrDetail{
|
||||
Type: "strict_mode",
|
||||
Type: "command_denied",
|
||||
Message: `strict mode is "bot", only bot-identity commands are available`,
|
||||
Hint: "if the user explicitly wants to switch policy, see `lark-cli config strict-mode --help` (confirm with the user before switching; switching does NOT require re-bind)",
|
||||
},
|
||||
@@ -371,7 +371,7 @@ func TestIntegration_StrictModeBot_ProfileOverride_DirectUserShortcutReturnsEnve
|
||||
assertEnvelope(t, code, output.ExitValidation, stdout, stderr, output.ErrorEnvelope{
|
||||
OK: false,
|
||||
Error: &output.ErrDetail{
|
||||
Type: "strict_mode",
|
||||
Type: "command_denied",
|
||||
Message: `strict mode is "bot", only bot-identity commands are available`,
|
||||
Hint: "if the user explicitly wants to switch policy, see `lark-cli config strict-mode --help` (confirm with the user before switching; switching does NOT require re-bind)",
|
||||
},
|
||||
@@ -409,7 +409,7 @@ func TestIntegration_StrictModeUser_ProfileOverride_ShortcutExplicitBotReturnsEn
|
||||
OK: false,
|
||||
Identity: "bot",
|
||||
Error: &output.ErrDetail{
|
||||
Type: "strict_mode",
|
||||
Type: "command_denied",
|
||||
Message: `strict mode is "user", only user-identity commands are available`,
|
||||
Hint: "if the user explicitly wants to switch policy, see `lark-cli config strict-mode --help` (confirm with the user before switching; switching does NOT require re-bind)",
|
||||
},
|
||||
@@ -428,7 +428,7 @@ func TestIntegration_StrictModeBot_ProfileOverride_ServiceExplicitUserReturnsEnv
|
||||
OK: false,
|
||||
Identity: "user",
|
||||
Error: &output.ErrDetail{
|
||||
Type: "strict_mode",
|
||||
Type: "command_denied",
|
||||
Message: `strict mode is "bot", only bot-identity commands are available`,
|
||||
Hint: "if the user explicitly wants to switch policy, see `lark-cli config strict-mode --help` (confirm with the user before switching; switching does NOT require re-bind)",
|
||||
},
|
||||
@@ -446,7 +446,7 @@ func TestIntegration_StrictModeUser_ProfileOverride_ServiceBotOnlyMethodReturnsE
|
||||
assertEnvelope(t, code, output.ExitValidation, stdout, stderr, output.ErrorEnvelope{
|
||||
OK: false,
|
||||
Error: &output.ErrDetail{
|
||||
Type: "strict_mode",
|
||||
Type: "command_denied",
|
||||
Message: `strict mode is "user", only user-identity commands are available`,
|
||||
Hint: "if the user explicitly wants to switch policy, see `lark-cli config strict-mode --help` (confirm with the user before switching; switching does NOT require re-bind)",
|
||||
},
|
||||
@@ -465,7 +465,7 @@ func TestIntegration_StrictModeBot_ProfileOverride_APIExplicitUserReturnsEnvelop
|
||||
OK: false,
|
||||
Identity: "user",
|
||||
Error: &output.ErrDetail{
|
||||
Type: "strict_mode",
|
||||
Type: "command_denied",
|
||||
Message: `strict mode is "bot", only bot-identity commands are available`,
|
||||
Hint: "if the user explicitly wants to switch policy, see `lark-cli config strict-mode --help` (confirm with the user before switching; switching does NOT require re-bind)",
|
||||
},
|
||||
|
||||
@@ -161,7 +161,7 @@ func (f *Factory) ResolveStrictMode(ctx context.Context) core.StrictMode {
|
||||
func (f *Factory) CheckStrictMode(ctx context.Context, as core.Identity) error {
|
||||
mode := f.ResolveStrictMode(ctx)
|
||||
if mode.IsActive() && !mode.AllowsIdentity(as) {
|
||||
return output.ErrWithHint(output.ExitValidation, "strict_mode",
|
||||
return output.ErrWithHint(output.ExitValidation, "command_denied",
|
||||
fmt.Sprintf("strict mode is %q, only %s-identity commands are available", mode, mode.ForcedIdentity()),
|
||||
"if the user explicitly wants to switch policy, see `lark-cli config strict-mode --help` (confirm with the user before switching; switching does NOT require re-bind)")
|
||||
}
|
||||
|
||||
@@ -324,8 +324,9 @@ var stderr = func() interface{ Write(p []byte) (int, error) } {
|
||||
//
|
||||
// V1 contract: a denial is signalled by the cobra annotation
|
||||
// "lark:pruning_denied_layer" being set on the command. The layer
|
||||
// value is the error.type ("pruning" / "strict_mode"); the source
|
||||
// follows the annotation "lark:pruning_denied_source".
|
||||
// value is the enforcement layer ("pruning" / "strict_mode") that
|
||||
// gets emitted as detail.layer in the envelope; the source follows
|
||||
// the annotation "lark:pruning_denied_source".
|
||||
//
|
||||
// This indirection lets us avoid an import cycle between hook and
|
||||
// pruning packages.
|
||||
|
||||
@@ -180,8 +180,8 @@ func TestApply_runEReturnsExitErrorAndCommandDeniedError(t *testing.T) {
|
||||
if exitErr.Detail == nil {
|
||||
t.Fatalf("ExitError.Detail required for envelope to render")
|
||||
}
|
||||
if exitErr.Detail.Type != "pruning" {
|
||||
t.Errorf("envelope error.type = %q, want pruning", exitErr.Detail.Type)
|
||||
if exitErr.Detail.Type != "command_denied" {
|
||||
t.Errorf("envelope error.type = %q, want command_denied", exitErr.Detail.Type)
|
||||
}
|
||||
// JSON envelope shape: detail.reason_code must be present and
|
||||
// match the closed enum.
|
||||
@@ -208,8 +208,8 @@ func TestApply_runEReturnsExitErrorAndCommandDeniedError(t *testing.T) {
|
||||
// Envelope round-trip sanity (the actual JSON cmd/root.go would emit).
|
||||
var buf strings.Builder
|
||||
output.WriteErrorEnvelope(&buf, exitErr, "user")
|
||||
if !strings.Contains(buf.String(), `"type": "pruning"`) {
|
||||
t.Errorf("envelope JSON missing type=pruning, got:\n%s", buf.String())
|
||||
if !strings.Contains(buf.String(), `"type": "command_denied"`) {
|
||||
t.Errorf("envelope JSON missing type=command_denied, got:\n%s", buf.String())
|
||||
}
|
||||
if !strings.Contains(buf.String(), `"reason_code": "write_not_allowed"`) {
|
||||
t.Errorf("envelope JSON missing reason_code, got:\n%s", buf.String())
|
||||
|
||||
@@ -102,7 +102,7 @@ func installDenyStub(cmd *cobra.Command, path string, d policydecision.Denial) {
|
||||
// Behaviour without this guard (pre-fix): a user yaml rule matching
|
||||
// a strict-mode stub's path would replace the RunE with the pruning
|
||||
// denyStub, hiding the original strict-mode error message AND
|
||||
// re-labelling error.type from "strict_mode" to "pruning".
|
||||
// re-labelling detail.layer from "strict_mode" to "pruning".
|
||||
if cmd.Annotations != nil &&
|
||||
cmd.Annotations[AnnotationDenialLayer] == policydecision.LayerStrictMode {
|
||||
return
|
||||
@@ -154,15 +154,13 @@ func installDenyStub(cmd *cobra.Command, path string, d policydecision.Denial) {
|
||||
ReasonCode: denial.ReasonCode,
|
||||
Reason: denial.Reason,
|
||||
}
|
||||
// Wrap in *output.ExitError so the cmd/root.go envelope writer
|
||||
// emits a JSON envelope. error.type uses denial.Layer ("pruning"
|
||||
// / "strict_mode") to match the design-doc contract. The detail
|
||||
// map carries the closed-enum reason_code plus the structured
|
||||
// fields agents read.
|
||||
// error.type is the user-facing semantic ("a command was denied by
|
||||
// policy"). detail.layer carries the implementation distinction
|
||||
// ("pruning" vs "strict_mode") for debugging.
|
||||
return &output.ExitError{
|
||||
Code: output.ExitValidation,
|
||||
Detail: &output.ErrDetail{
|
||||
Type: denial.Layer,
|
||||
Type: "command_denied",
|
||||
Message: cd.Error(),
|
||||
Detail: map[string]any{
|
||||
"path": cd.Path,
|
||||
|
||||
Reference in New Issue
Block a user