From 7109d923ec99d6ebe6657e8595da67bf361fbe88 Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Wed, 27 May 2026 10:49:02 +0200 Subject: [PATCH 1/5] feat: add kosli attest decision command (hidden/BETA) Implements `kosli attest decision` per kosli-dev/server#5695. Records a compliance decision against a control on a trail, posting to the /api/v2/attestations/{org}/{flow}/trail/{trail}/system endpoint with type_name="decision". The command is hidden (BETA). Co-Authored-By: Claude Sonnet 4.6 --- cmd/kosli/attest.go | 1 + cmd/kosli/attestDecision.go | 183 +++++++++++++++++++++++++++++++ cmd/kosli/attestDecision_test.go | 126 +++++++++++++++++++++ cmd/kosli/root.go | 1 + 4 files changed, 311 insertions(+) create mode 100644 cmd/kosli/attestDecision.go create mode 100644 cmd/kosli/attestDecision_test.go diff --git a/cmd/kosli/attest.go b/cmd/kosli/attest.go index 09167bf90..1632455bf 100644 --- a/cmd/kosli/attest.go +++ b/cmd/kosli/attest.go @@ -26,6 +26,7 @@ func newAttestCmd(out io.Writer) *cobra.Command { newAttestSonarCmd(out), newAttestCustomCmd(out), newAttestOverrideCmd(out), + newAttestDecisionCmd(out), ) return cmd } diff --git a/cmd/kosli/attestDecision.go b/cmd/kosli/attestDecision.go new file mode 100644 index 000000000..b7e91fb69 --- /dev/null +++ b/cmd/kosli/attestDecision.go @@ -0,0 +1,183 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "net/url" + "os" + + "github.com/kosli-dev/cli/internal/requests" + "github.com/spf13/cobra" +) + +type DecisionAttestationPayload struct { + *CommonAttestationPayload + TypeName string `json:"type_name"` + Control string `json:"control"` + Compliant bool `json:"is_compliant"` +} + +type attestDecisionOptions struct { + *CommonAttestationOptions + payload DecisionAttestationPayload +} + +const attestDecisionShortDesc = `[BETA] Record a compliance decision against a control in a Kosli trail. ` + +const attestDecisionLongDesc = attestDecisionShortDesc + ` +Use this command to record the outcome of evaluating a control as part of your delivery +pipeline — whether it was satisfied or not — attached to a specific trail with an optional artifact. +This decision is the evidence that a governance requirement was assessed. +` + attestationBindingDesc + ` + +` + commitDescription + +const attestDecisionExample = ` +# record a compliant decision against a trail: +kosli attest decision \ + --name yourAttestationName \ + --flow yourFlowName \ + --trail yourTrailName \ + --control RCTL-043 \ + --compliant=true \ + --api-token yourAPIToken \ + --org yourOrgName + +# record a non-compliant decision against a trail: +kosli attest decision \ + --name yourAttestationName \ + --flow yourFlowName \ + --trail yourTrailName \ + --control RCTL-043 \ + --compliant=false \ + --api-token yourAPIToken \ + --org yourOrgName + +# record a decision linked to a specific artifact (by fingerprint): +kosli attest decision \ + --name yourAttestationName \ + --flow yourFlowName \ + --trail yourTrailName \ + --control RCTL-043 \ + --compliant=true \ + --fingerprint yourArtifactFingerprint \ + --api-token yourAPIToken \ + --org yourOrgName + +# record a decision with an evidence attachment: +kosli attest decision \ + --name yourAttestationName \ + --flow yourFlowName \ + --trail yourTrailName \ + --control RCTL-043 \ + --compliant=true \ + --attachments eval-report.json \ + --api-token yourAPIToken \ + --org yourOrgName +` + +func newAttestDecisionCmd(out io.Writer) *cobra.Command { + o := &attestDecisionOptions{ + CommonAttestationOptions: &CommonAttestationOptions{ + fingerprintOptions: &fingerprintOptions{}, + }, + payload: DecisionAttestationPayload{ + CommonAttestationPayload: &CommonAttestationPayload{}, + TypeName: "decision", + }, + } + cmd := &cobra.Command{ + Use: "decision [IMAGE-NAME | FILE-PATH | DIR-PATH]", + Short: attestDecisionShortDesc, + Long: attestDecisionLongDesc, + Example: attestDecisionExample, + Hidden: true, + PreRunE: func(cmd *cobra.Command, args []string) error { + err := CustomMaximumNArgs(1, args) + if err != nil { + return err + } + + err = RequireGlobalFlags(global, []string{"Org", "ApiToken"}) + if err != nil { + return ErrorBeforePrintingUsage(cmd, err.Error()) + } + + if !cmd.Flags().Changed("compliant") { + return fmt.Errorf(`required flag(s) "compliant" not set`) + } + + err = MuXRequiredFlags(cmd, []string{"fingerprint", "artifact-type"}, false) + if err != nil { + return err + } + + err = ValidateSliceValues(o.redactedCommitInfo, allowedCommitRedactionValues) + if err != nil { + return fmt.Errorf("%s for --redact-commit-info", err.Error()) + } + + err = ValidateAttestationArtifactArg(args, o.fingerprintOptions.artifactType, o.payload.ArtifactFingerprint) + if err != nil { + return ErrorBeforePrintingUsage(cmd, err.Error()) + } + + return ValidateRegistryFlags(cmd, o.fingerprintOptions) + }, + + RunE: func(cmd *cobra.Command, args []string) error { + o.repoURLExplicit = cmd.Flags().Changed("repo-url") + return o.run(args) + }, + } + + ci := WhichCI() + addAttestationFlags(cmd, o.CommonAttestationOptions, o.payload.CommonAttestationPayload, ci) + cmd.Flags().StringVar(&o.payload.Control, "control", "", attestationDecisionControlFlag) + cmd.Flags().BoolVarP(&o.payload.Compliant, "compliant", "C", false, attestationCompliantFlag) + + err := RequireFlags(cmd, []string{"flow", "trail", "name", "control"}) + if err != nil { + logger.Error("failed to configure required flags: %v", err) + } + + return cmd +} + +func (o *attestDecisionOptions) run(args []string) error { + url, err := url.JoinPath(global.Host, "api/v2/attestations", global.Org, o.flowName, "trail", o.trailName, "system") + if err != nil { + return err + } + + err = o.CommonAttestationOptions.run(args, o.payload.CommonAttestationPayload) + if err != nil { + return err + } + + form, cleanupNeeded, evidencePath, err := prepareAttestationForm(o.payload, o.attachments) + if err != nil { + return err + } + if cleanupNeeded { + defer func() { + if err := os.Remove(evidencePath); err != nil { + logger.Warn("failed to remove evidence file: %v", err) + } + }() + } + + reqParams := &requests.RequestParams{ + Method: http.MethodPost, + URL: url, + Form: form, + DryRun: global.DryRun, + Token: global.ApiToken, + } + _, err = kosliClient.Do(reqParams) + if err == nil && !global.DryRun { + logger.Info("decision attestation '%s' is reported to trail: %s", o.payload.AttestationName, o.trailName) + } + return wrapAttestationError(err) +} diff --git a/cmd/kosli/attestDecision_test.go b/cmd/kosli/attestDecision_test.go new file mode 100644 index 000000000..b764622b6 --- /dev/null +++ b/cmd/kosli/attestDecision_test.go @@ -0,0 +1,126 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/suite" +) + +type AttestDecisionCommandTestSuite struct { + flowName string + trailName string + artifactFingerprint string + suite.Suite + defaultKosliArguments string +} + +func (suite *AttestDecisionCommandTestSuite) SetupTest() { + suite.flowName = "attest-decision" + suite.trailName = "test-123" + suite.artifactFingerprint = "7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9" + global = &GlobalOpts{ + ApiToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImNkNzg4OTg5In0.e8i_lA_QrEhFncb05Xw6E_tkCHU9QfcY4OLTVUCHffY", + Org: "docs-cmd-test-user", + Host: "http://localhost:8001", + } + suite.defaultKosliArguments = fmt.Sprintf(" --flow %s --trail %s --repo-root ../.. --host %s --org %s --api-token %s", suite.flowName, suite.trailName, global.Host, global.Org, global.ApiToken) + CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.T()) + BeginTrail(suite.trailName, suite.flowName, "", suite.T()) + CreateArtifactOnTrail(suite.flowName, suite.trailName, "cli", suite.artifactFingerprint, "file1", suite.T()) +} + +func (suite *AttestDecisionCommandTestSuite) TestAttestDecisionCmd() { + tests := []cmdTestCase{ + { + wantError: true, + name: "fails when more arguments are provided", + cmd: fmt.Sprintf("attest decision foo bar %s --control RCTL-043 --compliant=true", suite.defaultKosliArguments), + golden: "Error: accepts at most 1 arg(s), received 2 [foo bar]\n", + }, + { + wantError: true, + name: "fails when missing --name flag", + cmd: fmt.Sprintf("attest decision %s --control RCTL-043 --compliant=true", suite.defaultKosliArguments), + golden: "Error: required flag(s) \"name\" not set\n", + }, + { + wantError: true, + name: "fails when missing --control flag", + cmd: fmt.Sprintf("attest decision --name foo %s --compliant=true", suite.defaultKosliArguments), + golden: "Error: required flag(s) \"control\" not set\n", + }, + { + wantError: true, + name: "fails when --compliant is not set", + cmd: fmt.Sprintf("attest decision --name foo --control RCTL-043 %s", suite.defaultKosliArguments), + golden: "Error: required flag(s) \"compliant\" not set\n", + }, + { + wantError: true, + name: "fails when both --fingerprint and --artifact-type are set", + cmd: fmt.Sprintf("attest decision testdata/file1 --fingerprint xxxx --artifact-type file --name foo --control RCTL-043 --compliant=true %s", suite.defaultKosliArguments), + golden: "Error: only one of --fingerprint, --artifact-type is allowed\n", + }, + { + wantError: true, + name: "fails when --fingerprint is not a valid SHA256", + cmd: fmt.Sprintf("attest decision --name foo --fingerprint xxxx --control RCTL-043 --compliant=true %s", suite.defaultKosliArguments), + golden: "Error: xxxx is not a valid SHA256 fingerprint. It should match the pattern ^([a-f0-9]{64})$\nUsage: kosli attest decision [IMAGE-NAME | FILE-PATH | DIR-PATH] [flags]\n", + }, + { + wantError: true, + name: "fails when --name is passed as empty string", + cmd: fmt.Sprintf("attest decision --name \"\" --control RCTL-043 --compliant=true %s", suite.defaultKosliArguments), + golden: "Error: flag '--name' is required, but empty string was provided\n", + }, + { + name: "can record a compliant decision against a trail", + cmd: fmt.Sprintf("attest decision --name foo --control RCTL-043 --compliant=true %s", suite.defaultKosliArguments), + golden: "decision attestation 'foo' is reported to trail: test-123\n", + }, + { + name: "can record a non-compliant decision against a trail", + cmd: fmt.Sprintf("attest decision --name foo --control RCTL-043 --compliant=false %s", suite.defaultKosliArguments), + golden: "decision attestation 'foo' is reported to trail: test-123\n", + }, + { + name: "can record a decision linked to a specific artifact by fingerprint", + cmd: fmt.Sprintf("attest decision --fingerprint 7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9 --name foo --control RCTL-043 --compliant=true %s", suite.defaultKosliArguments), + golden: "decision attestation 'foo' is reported to trail: test-123\n", + }, + { + name: "can record a decision with an attachment", + cmd: fmt.Sprintf("attest decision --name foo --control RCTL-043 --compliant=true --attachments testdata/file1 %s", suite.defaultKosliArguments), + golden: "decision attestation 'foo' is reported to trail: test-123\n", + }, + { + name: "can record a decision with description", + cmd: fmt.Sprintf("attest decision --name foo --control RCTL-043 --compliant=true --description 'evaluation passed' %s", suite.defaultKosliArguments), + golden: "decision attestation 'foo' is reported to trail: test-123\n", + }, + { + name: "can record a decision with annotations", + cmd: fmt.Sprintf("attest decision --name foo --control RCTL-043 --compliant=true --annotate key=value %s", suite.defaultKosliArguments), + golden: "decision attestation 'foo' is reported to trail: test-123\n", + }, + { + wantError: true, + name: "fails when annotation key is invalid", + cmd: fmt.Sprintf("attest decision --name foo --control RCTL-043 --compliant=true --annotate foo.bar=baz %s", suite.defaultKosliArguments), + golden: "Error: --annotate flag should be in the format key=value. Invalid key: 'foo.bar'. Key can only contain [A-Za-z0-9_]\n", + }, + { + wantError: true, + name: "fails when --name has invalid dot format", + cmd: fmt.Sprintf("attest decision --name .foo --control RCTL-043 --compliant=true %s", suite.defaultKosliArguments), + golden: "Error: failed to parse attestation name: invalid attestation name format: .foo\n", + }, + } + + runTestCmd(suite.T(), tests) +} + +func TestAttestDecisionCommandTestSuite(t *testing.T) { + suite.Run(t, new(AttestDecisionCommandTestSuite)) +} diff --git a/cmd/kosli/root.go b/cmd/kosli/root.go index 81012b22a..0bd1b4177 100644 --- a/cmd/kosli/root.go +++ b/cmd/kosli/root.go @@ -237,6 +237,7 @@ The ^.kosli_ignore^ will be treated as part of the artifact like any other file, attestationOverrideReasonFlag = "The reason for overriding the attestation." newComplianceStatusFlag = "The new compliance status to set on the attestation. A boolean flag https://docs.kosli.com/faq/#boolean-flags" originalAttestationTypeFlag = "The original attestation type being overridden (e.g. generic, snyk, junit, sonar, jira, pull_request, custom)." + attestationDecisionControlFlag = "The control identifier being evaluated (e.g. RCTL-043)." excludeScalingFlag = "[optional] Exclude scaling events for snapshots. Snapshots with scaling changes will not result in new environment records." includeScalingFlag = "[optional] Include scaling events for snapshots. Snapshots with scaling changes will result in new environment records." includedEnvironments = "[optional] Comma separated list of environments to include in logical environment" From 9158617cf9496e8389bf2beda3db5f9f3f1b45dc Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Wed, 27 May 2026 10:54:11 +0200 Subject: [PATCH 2/5] fix: nest decision data inside attestation_data field The /system endpoint requires control and is_compliant nested under attestation_data rather than at the top-level payload, matching the server's schema validation. Co-Authored-By: Claude Sonnet 4.6 --- cmd/kosli/attestDecision.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cmd/kosli/attestDecision.go b/cmd/kosli/attestDecision.go index b7e91fb69..ca98294ee 100644 --- a/cmd/kosli/attestDecision.go +++ b/cmd/kosli/attestDecision.go @@ -11,13 +11,17 @@ import ( "github.com/spf13/cobra" ) -type DecisionAttestationPayload struct { - *CommonAttestationPayload - TypeName string `json:"type_name"` +type DecisionAttestationData struct { Control string `json:"control"` Compliant bool `json:"is_compliant"` } +type DecisionAttestationPayload struct { + *CommonAttestationPayload + TypeName string `json:"type_name"` + AttestationData DecisionAttestationData `json:"attestation_data"` +} + type attestDecisionOptions struct { *CommonAttestationOptions payload DecisionAttestationPayload @@ -134,8 +138,8 @@ func newAttestDecisionCmd(out io.Writer) *cobra.Command { ci := WhichCI() addAttestationFlags(cmd, o.CommonAttestationOptions, o.payload.CommonAttestationPayload, ci) - cmd.Flags().StringVar(&o.payload.Control, "control", "", attestationDecisionControlFlag) - cmd.Flags().BoolVarP(&o.payload.Compliant, "compliant", "C", false, attestationCompliantFlag) + cmd.Flags().StringVar(&o.payload.AttestationData.Control, "control", "", attestationDecisionControlFlag) + cmd.Flags().BoolVarP(&o.payload.AttestationData.Compliant, "compliant", "C", false, attestationCompliantFlag) err := RequireFlags(cmd, []string{"flow", "trail", "name", "control"}) if err != nil { From 63e962ae2c73a63284c18f8fd95fd147073e0ec0 Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Wed, 27 May 2026 11:05:31 +0200 Subject: [PATCH 3/5] fix: correct payload structure and create control in test setup - Move control/is_compliant back to top-level payload fields; keep attestation_data as a required-but-empty field (reserved for future structured inputs per server schema) - Fix flag bindings to point to top-level payload fields - Add CreateControl helper to testHelpers.go (POST /api/v2/controls/{org}) - Call CreateControl in SetupTest so RCTL-043 exists before attesting Co-Authored-By: Claude Sonnet 4.6 --- cmd/kosli/attestDecision.go | 16 +++++++--------- cmd/kosli/attestDecision_test.go | 1 + cmd/kosli/testHelpers.go | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/cmd/kosli/attestDecision.go b/cmd/kosli/attestDecision.go index ca98294ee..8473ce56b 100644 --- a/cmd/kosli/attestDecision.go +++ b/cmd/kosli/attestDecision.go @@ -11,15 +11,12 @@ import ( "github.com/spf13/cobra" ) -type DecisionAttestationData struct { - Control string `json:"control"` - Compliant bool `json:"is_compliant"` -} - type DecisionAttestationPayload struct { *CommonAttestationPayload - TypeName string `json:"type_name"` - AttestationData DecisionAttestationData `json:"attestation_data"` + TypeName string `json:"type_name"` + Control string `json:"control"` + Compliant bool `json:"is_compliant"` + AttestationData interface{} `json:"attestation_data"` } type attestDecisionOptions struct { @@ -89,6 +86,7 @@ func newAttestDecisionCmd(out io.Writer) *cobra.Command { payload: DecisionAttestationPayload{ CommonAttestationPayload: &CommonAttestationPayload{}, TypeName: "decision", + AttestationData: map[string]interface{}{}, }, } cmd := &cobra.Command{ @@ -138,8 +136,8 @@ func newAttestDecisionCmd(out io.Writer) *cobra.Command { ci := WhichCI() addAttestationFlags(cmd, o.CommonAttestationOptions, o.payload.CommonAttestationPayload, ci) - cmd.Flags().StringVar(&o.payload.AttestationData.Control, "control", "", attestationDecisionControlFlag) - cmd.Flags().BoolVarP(&o.payload.AttestationData.Compliant, "compliant", "C", false, attestationCompliantFlag) + cmd.Flags().StringVar(&o.payload.Control, "control", "", attestationDecisionControlFlag) + cmd.Flags().BoolVarP(&o.payload.Compliant, "compliant", "C", false, attestationCompliantFlag) err := RequireFlags(cmd, []string{"flow", "trail", "name", "control"}) if err != nil { diff --git a/cmd/kosli/attestDecision_test.go b/cmd/kosli/attestDecision_test.go index b764622b6..ec3f6e0fb 100644 --- a/cmd/kosli/attestDecision_test.go +++ b/cmd/kosli/attestDecision_test.go @@ -25,6 +25,7 @@ func (suite *AttestDecisionCommandTestSuite) SetupTest() { Host: "http://localhost:8001", } suite.defaultKosliArguments = fmt.Sprintf(" --flow %s --trail %s --repo-root ../.. --host %s --org %s --api-token %s", suite.flowName, suite.trailName, global.Host, global.Org, global.ApiToken) + CreateControl(global.Org, "RCTL-043", "Test Control", suite.T()) CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.T()) BeginTrail(suite.trailName, suite.flowName, "", suite.T()) CreateArtifactOnTrail(suite.flowName, suite.trailName, "cli", suite.artifactFingerprint, "file1", suite.T()) diff --git a/cmd/kosli/testHelpers.go b/cmd/kosli/testHelpers.go index 8d96ed8c9..8d5ccabc6 100644 --- a/cmd/kosli/testHelpers.go +++ b/cmd/kosli/testHelpers.go @@ -6,6 +6,8 @@ import ( "encoding/json" "fmt" "io" + "net/http" + "net/url" "os" "path/filepath" "regexp" @@ -14,6 +16,7 @@ import ( "testing" "github.com/kosli-dev/cli/internal/gitview" + "github.com/kosli-dev/cli/internal/requests" shellwords "github.com/mattn/go-shellwords" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -518,6 +521,22 @@ func UnSetEnvVars(envVars map[string]string, t *testing.T) { } } +// CreateControl creates a control in the org via the API. +func CreateControl(org, identifier, name string, t *testing.T) { + t.Helper() + u, err := url.JoinPath(global.Host, "api/v2/controls", org) + require.NoError(t, err, "control URL should be constructed without error") + + reqParams := &requests.RequestParams{ + Method: http.MethodPost, + URL: u, + Payload: map[string]string{"identifier": identifier, "name": name}, + Token: global.ApiToken, + } + _, err = kosliClient.Do(reqParams) + require.NoError(t, err, "control should be created without error") +} + // CreatePolicy creates a policy on the server func CreatePolicy(org, policyName string, t *testing.T) { t.Helper() From da49273a9cc7742312ae00d9c6a846aed0fdd787 Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Wed, 27 May 2026 11:10:26 +0200 Subject: [PATCH 4/5] test: add user-data test case for attest decision Co-Authored-By: Claude Sonnet 4.6 --- cmd/kosli/attestDecision_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/kosli/attestDecision_test.go b/cmd/kosli/attestDecision_test.go index ec3f6e0fb..5cc55e83b 100644 --- a/cmd/kosli/attestDecision_test.go +++ b/cmd/kosli/attestDecision_test.go @@ -105,6 +105,11 @@ func (suite *AttestDecisionCommandTestSuite) TestAttestDecisionCmd() { cmd: fmt.Sprintf("attest decision --name foo --control RCTL-043 --compliant=true --annotate key=value %s", suite.defaultKosliArguments), golden: "decision attestation 'foo' is reported to trail: test-123\n", }, + { + name: "can record a decision with user data", + cmd: fmt.Sprintf("attest decision --name foo --control RCTL-043 --compliant=true --user-data testdata/person-type-data-example.json %s", suite.defaultKosliArguments), + golden: "decision attestation 'foo' is reported to trail: test-123\n", + }, { wantError: true, name: "fails when annotation key is invalid", From 779fe0bc1db63ca8091b733799265725b028d7c6 Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Wed, 27 May 2026 11:13:50 +0200 Subject: [PATCH 5/5] fix: move is_compliant inside attestation_data, keep control at top level The server schema requires control as a top-level field and is_compliant nested under attestation_data. Co-Authored-By: Claude Sonnet 4.6 --- cmd/kosli/attestDecision.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/kosli/attestDecision.go b/cmd/kosli/attestDecision.go index 8473ce56b..59c4d07e8 100644 --- a/cmd/kosli/attestDecision.go +++ b/cmd/kosli/attestDecision.go @@ -11,12 +11,15 @@ import ( "github.com/spf13/cobra" ) +type DecisionAttestationData struct { + Compliant bool `json:"is_compliant"` +} + type DecisionAttestationPayload struct { *CommonAttestationPayload - TypeName string `json:"type_name"` - Control string `json:"control"` - Compliant bool `json:"is_compliant"` - AttestationData interface{} `json:"attestation_data"` + TypeName string `json:"type_name"` + Control string `json:"control"` + AttestationData DecisionAttestationData `json:"attestation_data"` } type attestDecisionOptions struct { @@ -86,7 +89,6 @@ func newAttestDecisionCmd(out io.Writer) *cobra.Command { payload: DecisionAttestationPayload{ CommonAttestationPayload: &CommonAttestationPayload{}, TypeName: "decision", - AttestationData: map[string]interface{}{}, }, } cmd := &cobra.Command{ @@ -137,7 +139,7 @@ func newAttestDecisionCmd(out io.Writer) *cobra.Command { ci := WhichCI() addAttestationFlags(cmd, o.CommonAttestationOptions, o.payload.CommonAttestationPayload, ci) cmd.Flags().StringVar(&o.payload.Control, "control", "", attestationDecisionControlFlag) - cmd.Flags().BoolVarP(&o.payload.Compliant, "compliant", "C", false, attestationCompliantFlag) + cmd.Flags().BoolVarP(&o.payload.AttestationData.Compliant, "compliant", "C", false, attestationCompliantFlag) err := RequireFlags(cmd, []string{"flow", "trail", "name", "control"}) if err != nil {