From e24d7280619743561448084ae80eb9be17c129ff Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Tue, 29 Sep 2026 14:20:26 +0000 Subject: [PATCH 1/3] cligen: omit immutable (create-only) fields from update-command flags An IMMUTABLE (AIP-203), non-computed request-body field is settable at create but cannot change on update. Such fields now keep their CLI flag on create-type commands (and any non-update command) and are omitted from update-type commands. Computed/output-only fields are unchanged and stay flagless everywhere, so a field that is both immutable and computed (e.g. dashboards parent_path) is unaffected. This wires the IMMUTABLE behavior and the update-crud signal from cli.json into the render model: - internal/clijson/types.go: FieldJSON.IsImmutable, MethodJSON.IsCrudUpdate (regenerated verbatim from the genkit contract). - transform.go / model.go: propagate both into the CLI render model. - templates/service.go.tmpl: skip immutable, non-computed fields on update-type methods in both the flag-declaration and well-known-type parse loops. Depends on the matching genkit change (deco/genkit/rendering/cli) landing in universe so a future generate-clijson emits is_immutable/is_crud_update. Co-authored-by: Isaac --- .nextchanges/cli/immutable-create-only-flags.md | 1 + internal/cligen/model.go | 2 ++ internal/cligen/templates/service.go.tmpl | 9 ++++++++- internal/cligen/transform.go | 2 ++ internal/clijson/types.go | 15 ++++++++++----- 5 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 .nextchanges/cli/immutable-create-only-flags.md diff --git a/.nextchanges/cli/immutable-create-only-flags.md b/.nextchanges/cli/immutable-create-only-flags.md new file mode 100644 index 00000000000..f8cdd794393 --- /dev/null +++ b/.nextchanges/cli/immutable-create-only-flags.md @@ -0,0 +1 @@ +* Omit command-line flags for immutable (create-only) request fields from update commands; set such fields at create time or via `--json`. diff --git a/internal/cligen/model.go b/internal/cligen/model.go index ae5ce44dd63..15ab313eca5 100644 --- a/internal/cligen/model.go +++ b/internal/cligen/model.go @@ -143,6 +143,7 @@ type MethodJSON struct { IsJsonOnly bool `json:"is_json_only,omitempty"` IsCrudCreate bool `json:"is_crud_create,omitempty"` IsCrudRead bool `json:"is_crud_read,omitempty"` + IsCrudUpdate bool `json:"is_crud_update,omitempty"` IsHiddenCLI bool `json:"is_hidden_cli,omitempty"` IsResponseByteStream bool `json:"is_response_byte_stream,omitempty"` HasRequiredPositionalArguments bool `json:"has_required_positional_arguments,omitempty"` @@ -192,6 +193,7 @@ type FieldJSON struct { IsQuery bool `json:"is_query,omitempty"` IsComputed bool `json:"is_computed,omitempty"` IsOutputOnly bool `json:"is_output_only,omitempty"` + IsImmutable bool `json:"is_immutable,omitempty"` IsRequestBodyField bool `json:"is_request_body_field,omitempty"` IsOptionalObject bool `json:"is_optional_object,omitempty"` diff --git a/internal/cligen/templates/service.go.tmpl b/internal/cligen/templates/service.go.tmpl index a13724dca2b..e2a1846fa17 100644 --- a/internal/cligen/templates/service.go.tmpl +++ b/internal/cligen/templates/service.go.tmpl @@ -221,6 +221,13 @@ func new{{.PascalName}}() *cobra.Command { {{else if .Entity.MapValue }}// TODO: map via StringToStringVar: {{.Name}} {{else if .IsComputed -}} {{else if .IsOutputOnly -}} + {{- /* Immutable (create-only) fields are settable at create but cannot + change on update. They keep their flag on create-type commands + (and any non-update command) and are omitted from update-type + commands. Computed/output-only fields are handled above and stay + flagless everywhere, so a field that is both immutable and + computed (e.g. dashboards parent_path) is unaffected by this. */ -}} + {{else if and .IsImmutable $method.IsCrudUpdate -}} {{else if .Entity.Enum }}cmd.Flags().Var(&{{- template "request-body-obj" (dict "Method" $method "Field" .)}}, "{{.KebabName}}", `{{.Summary | without "`" | trimSuffix "."}}{{if .Summary}}. {{end}}Supported values: {{template "printArray" .Entity.Enum}}{{if $hasFieldMask}}. Wire name: '{{.Name}}'.{{end}}`) {{else if or .Entity.IsDuration .Entity.IsTimestamp .Entity.IsFieldMask}} var {{.CamelName}}Param string cmd.Flags().StringVar(&{{.CamelName}}Param, "{{.KebabName}}", {{.CamelName}}Param, `{{.Summary | without "`" | trimSuffix "."}}{{if .Summary}}.{{end}}{{if $hasFieldMask}} Wire name: '{{.Name}}'.{{end}}`) @@ -403,7 +410,7 @@ func new{{.PascalName}}() *cobra.Command { {{- template "args-scan" (dict "Arg" $arg "Field" $field "Method" $method "HasIdPrompt" $hasIdPrompt "ExcludeFromJson" $excludeFromJson)}} {{- end -}} {{- range .AllFields -}} - {{- if and (not .Required) (not .IsComputed) (not .IsOutputOnly)}} + {{- if and (not .Required) (not .IsComputed) (not .IsOutputOnly) (not (and .IsImmutable $method.IsCrudUpdate))}} {{if or .Entity.IsDuration .Entity.IsTimestamp .Entity.IsFieldMask}} if {{.CamelName}}Param != "" { {{template "parse-well-known-type" (dict "Method" $method "Field" . "SourceValue" (printf "%sParam" .CamelName))}} diff --git a/internal/cligen/transform.go b/internal/cligen/transform.go index ea7864877e0..82a62122668 100644 --- a/internal/cligen/transform.go +++ b/internal/cligen/transform.go @@ -130,6 +130,7 @@ func fromMethod(m *clijson.MethodJSON) *MethodJSON { IsJsonOnly: m.IsJsonOnly, IsCrudCreate: m.IsCrudCreate, IsCrudRead: m.IsCrudRead, + IsCrudUpdate: m.IsCrudUpdate, IsHiddenCLI: m.IsHiddenCLI, IsResponseByteStream: m.IsResponseByteStream, HasRequiredPositionalArguments: m.HasRequiredPositionalArguments, @@ -161,6 +162,7 @@ func fromField(f *clijson.FieldJSON) *FieldJSON { IsQuery: f.IsQuery, IsComputed: f.IsComputed, IsOutputOnly: f.IsOutputOnly, + IsImmutable: f.IsImmutable, IsRequestBodyField: f.IsRequestBodyField, IsOptionalObject: f.IsOptionalObject, Entity: fromEntity(f.Entity), diff --git a/internal/clijson/types.go b/internal/clijson/types.go index 7b0f02277e1..6ac602e05e8 100644 --- a/internal/clijson/types.go +++ b/internal/clijson/types.go @@ -117,6 +117,7 @@ type MethodJSON struct { IsJsonOnly bool `json:"is_json_only,omitempty"` IsCrudCreate bool `json:"is_crud_create,omitempty"` IsCrudRead bool `json:"is_crud_read,omitempty"` + IsCrudUpdate bool `json:"is_crud_update,omitempty"` IsHiddenCLI bool `json:"is_hidden_cli,omitempty"` IsResponseByteStream bool `json:"is_response_byte_stream,omitempty"` HasRequiredPositionalArguments bool `json:"has_required_positional_arguments,omitempty"` @@ -145,11 +146,15 @@ type FieldJSON struct { Description string `json:"description,omitempty"` - Required bool `json:"required,omitempty"` - IsPath bool `json:"is_path,omitempty"` - IsQuery bool `json:"is_query,omitempty"` - IsComputed bool `json:"is_computed,omitempty"` - IsOutputOnly bool `json:"is_output_only,omitempty"` + Required bool `json:"required,omitempty"` + IsPath bool `json:"is_path,omitempty"` + IsQuery bool `json:"is_query,omitempty"` + IsComputed bool `json:"is_computed,omitempty"` + IsOutputOnly bool `json:"is_output_only,omitempty"` + // IsImmutable is the AIP-203 IMMUTABLE behavior: settable at create but not + // changeable on update. The CLI keeps the flag on create-type commands and + // omits it from update-type commands. + IsImmutable bool `json:"is_immutable,omitempty"` IsRequestBodyField bool `json:"is_request_body_field,omitempty"` IsOptionalObject bool `json:"is_optional_object,omitempty"` From cb049c2689c25fbebf9d0ffc2875827e814eefb0 Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Tue, 3 Nov 2026 12:29:59 +0000 Subject: [PATCH 2/3] Add PR link to changelog fragment Co-authored-by: Isaac --- .nextchanges/cli/immutable-create-only-flags.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nextchanges/cli/immutable-create-only-flags.md b/.nextchanges/cli/immutable-create-only-flags.md index f8cdd794393..f61ec450dc7 100644 --- a/.nextchanges/cli/immutable-create-only-flags.md +++ b/.nextchanges/cli/immutable-create-only-flags.md @@ -1 +1 @@ -* Omit command-line flags for immutable (create-only) request fields from update commands; set such fields at create time or via `--json`. +* Omit command-line flags for immutable (create-only) request fields from update commands; set such fields at create time or via `--json`. ([#7309](https://github.com/databricks/cli/pull/7309)) From 88fb3cc9939b128d7bff1d60c27223f228d00d26 Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Tue, 3 Nov 2026 13:51:16 +0000 Subject: [PATCH 3/3] Drop changelog fragment until the regen lands The template change alone has no effect on any existing command (cli.json does not carry the new keys yet), so the user-visible entry belongs with the regeneration that removes flags. Co-authored-by: Isaac --- .nextchanges/cli/immutable-create-only-flags.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .nextchanges/cli/immutable-create-only-flags.md diff --git a/.nextchanges/cli/immutable-create-only-flags.md b/.nextchanges/cli/immutable-create-only-flags.md deleted file mode 100644 index f61ec450dc7..00000000000 --- a/.nextchanges/cli/immutable-create-only-flags.md +++ /dev/null @@ -1 +0,0 @@ -* Omit command-line flags for immutable (create-only) request fields from update commands; set such fields at create time or via `--json`. ([#7309](https://github.com/databricks/cli/pull/7309))