{__("No controls linked")}
@@ -293,12 +290,12 @@ export default function StatementOfApplicabilityControlsTab({
: (
- {control.implemented === "IMPLEMENTED" ? __("Yes") : __("No")}
+ {getControlMaturityLevelLabel(__, control.maturityLevel)}
- {control.implemented === "NOT_IMPLEMENTED" && control.notImplementedJustification && (
+ {control.maturityLevel === "NONE" && control.notImplementedJustification && (
{control.notImplementedJustification}
@@ -306,17 +303,6 @@ export default function StatementOfApplicabilityControlsTab({
)}
|
-
- {control.applicability === false
- ? -
- : (
-
- {control.maturityLevel
- ? getControlMaturityLevelLabel(__, control.maturityLevel)
- : __("Not set")}
-
- )}
- |
{control.applicability === false
? -
diff --git a/cmd/migrate-soa-snapshots-to-documents/main.go b/cmd/migrate-soa-snapshots-to-documents/main.go
index 728585cc4..40622037f 100644
--- a/cmd/migrate-soa-snapshots-to-documents/main.go
+++ b/cmd/migrate-soa-snapshots-to-documents/main.go
@@ -370,7 +370,7 @@ type snapshotControl struct {
applicability bool
justification *string
bestPractice bool
- implemented string
+ maturityLevel string
notImplementedJustification *string
hasLegal bool
hasContractual bool
@@ -403,7 +403,7 @@ SELECT
stmt.applicability,
stmt.justification,
c.best_practice,
- c.implemented,
+ c.maturity_level,
c.not_implemented_justification,
EXISTS (
SELECT 1 FROM controls_obligations co
@@ -442,7 +442,7 @@ ORDER BY f.name, c.section_title;
&sc.applicability,
&sc.justification,
&sc.bestPractice,
- &sc.implemented,
+ &sc.maturityLevel,
&sc.notImplementedJustification,
&sc.hasLegal,
&sc.hasContractual,
@@ -458,17 +458,13 @@ ORDER BY f.name, c.section_title;
justification = *sc.justification
}
- implemented := "-"
+ maturityLevel := "-"
if applicable {
- if sc.implemented == "IMPLEMENTED" {
- implemented = "Yes"
- } else {
- implemented = "No"
- }
+ maturityLevel = docgen.MaturityLabel(coredata.ControlMaturityLevel(sc.maturityLevel))
}
notImplJustification := "-"
- if applicable && sc.implemented != "IMPLEMENTED" && sc.notImplementedJustification != nil {
+ if applicable && sc.maturityLevel == "NONE" && sc.notImplementedJustification != nil {
notImplJustification = *sc.notImplementedJustification
}
@@ -489,7 +485,7 @@ ORDER BY f.name, c.section_title;
ControlName: sc.controlName,
Applicability: docgen.BoolLabel(applicable),
Justification: justification,
- Implemented: implemented,
+ MaturityLevel: maturityLevel,
NotImplJustification: notImplJustification,
Regulatory: regulatory,
Contractual: contractual,
diff --git a/e2e/console/control_test.go b/e2e/console/control_test.go
index f4fbb4531..bb1f90035 100644
--- a/e2e/console/control_test.go
+++ b/e2e/console/control_test.go
@@ -59,12 +59,12 @@ func TestControl_Create(t *testing.T) {
err := owner.Execute(query, map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "sectionTitle": "A.5",
- "name": "Information Security Policies",
- "description": "Policies for information security",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "sectionTitle": "A.5",
+ "name": "Information Security Policies",
+ "description": "Policies for information security",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
}, &result)
require.NoError(t, err)
@@ -266,11 +266,11 @@ func TestControl_RequiredFields(t *testing.T) {
name: "Missing frameworkId should fail",
variables: map[string]any{
"input": map[string]any{
- "name": "Test Control",
- "description": "Test",
- "sectionTitle": "Section 1",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "name": "Test Control",
+ "description": "Test",
+ "sectionTitle": "Section 1",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
},
wantError: true,
@@ -279,11 +279,11 @@ func TestControl_RequiredFields(t *testing.T) {
name: "Missing name should fail",
variables: map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "description": "Test",
- "sectionTitle": "Section 1",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "description": "Test",
+ "sectionTitle": "Section 1",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
},
wantError: true,
@@ -292,11 +292,11 @@ func TestControl_RequiredFields(t *testing.T) {
name: "Missing sectionTitle should fail",
variables: map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "name": "Test Control",
- "description": "Test",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "name": "Test Control",
+ "description": "Test",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
},
wantError: true,
@@ -305,11 +305,11 @@ func TestControl_RequiredFields(t *testing.T) {
name: "Missing description should fail (required field)",
variables: map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "name": "Test Control",
- "sectionTitle": "Section 1",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "name": "Test Control",
+ "sectionTitle": "Section 1",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
},
wantError: true,
@@ -318,17 +318,17 @@ func TestControl_RequiredFields(t *testing.T) {
name: "Missing bestPractice should fail",
variables: map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "name": "Test Control",
- "description": "Test",
- "sectionTitle": "Section 1",
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "name": "Test Control",
+ "description": "Test",
+ "sectionTitle": "Section 1",
+ "maturityLevel": "INITIAL",
},
},
wantError: true,
},
{
- name: "Missing implemented should fail",
+ name: "Missing maturityLevel should fail",
variables: map[string]any{
"input": map[string]any{
"frameworkId": frameworkID,
@@ -418,12 +418,12 @@ func TestControl_OmittableDescription(t *testing.T) {
err = owner.Execute(createControlQuery, map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "name": "Omittable Test Control",
- "description": "Initial description",
- "sectionTitle": "Section 1",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "name": "Omittable Test Control",
+ "description": "Initial description",
+ "sectionTitle": "Section 1",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
}, &createResult)
require.NoError(t, err)
@@ -555,8 +555,8 @@ func TestControl_MaturityLevel(t *testing.T) {
CreateControl struct {
ControlEdge struct {
Node struct {
- ID string `json:"id"`
- MaturityLevel *string `json:"maturityLevel"`
+ ID string `json:"id"`
+ MaturityLevel string `json:"maturityLevel"`
} `json:"node"`
} `json:"controlEdge"`
} `json:"createControl"`
@@ -565,26 +565,26 @@ func TestControl_MaturityLevel(t *testing.T) {
type updateResult struct {
UpdateControl struct {
Control struct {
- ID string `json:"id"`
- MaturityLevel *string `json:"maturityLevel"`
+ ID string `json:"id"`
+ MaturityLevel string `json:"maturityLevel"`
} `json:"control"`
} `json:"updateControl"`
}
- t.Run("create without maturityLevel returns null", func(t *testing.T) {
+ t.Run("create with INITIAL maturityLevel", func(t *testing.T) {
var res createResult
err := owner.Execute(createControlQuery, map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "sectionTitle": "M.1",
- "name": "Control without maturity",
- "description": "control without maturity description",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "sectionTitle": "M.1",
+ "name": "Control with initial maturity",
+ "description": "control with initial maturity description",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
}, &res)
require.NoError(t, err)
- assert.Nil(t, res.CreateControl.ControlEdge.Node.MaturityLevel)
+ assert.Equal(t, "INITIAL", res.CreateControl.ControlEdge.Node.MaturityLevel)
})
t.Run("create with maturityLevel persists value", func(t *testing.T) {
@@ -596,25 +596,23 @@ func TestControl_MaturityLevel(t *testing.T) {
"name": "Control with maturity",
"description": "control with maturity description",
"bestPractice": true,
- "implemented": "IMPLEMENTED",
"maturityLevel": "DEFINED",
},
}, &res)
require.NoError(t, err)
- require.NotNil(t, res.CreateControl.ControlEdge.Node.MaturityLevel)
- assert.Equal(t, "DEFINED", *res.CreateControl.ControlEdge.Node.MaturityLevel)
+ assert.Equal(t, "DEFINED", res.CreateControl.ControlEdge.Node.MaturityLevel)
})
- t.Run("update lifecycle: set, change, clear, omit", func(t *testing.T) {
+ t.Run("update lifecycle: set, change, omit", func(t *testing.T) {
var created createResult
err := owner.Execute(createControlQuery, map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "sectionTitle": "M.3",
- "name": "Lifecycle control",
- "description": "lifecycle control description",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "sectionTitle": "M.3",
+ "name": "Lifecycle control",
+ "description": "lifecycle control description",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
}, &created)
require.NoError(t, err)
@@ -629,8 +627,7 @@ func TestControl_MaturityLevel(t *testing.T) {
},
}, &setRes)
require.NoError(t, err)
- require.NotNil(t, setRes.UpdateControl.Control.MaturityLevel)
- assert.Equal(t, "INITIAL", *setRes.UpdateControl.Control.MaturityLevel)
+ assert.Equal(t, "INITIAL", setRes.UpdateControl.Control.MaturityLevel)
// change
var changeRes updateResult
@@ -641,30 +638,9 @@ func TestControl_MaturityLevel(t *testing.T) {
},
}, &changeRes)
require.NoError(t, err)
- require.NotNil(t, changeRes.UpdateControl.Control.MaturityLevel)
- assert.Equal(t, "OPTIMIZING", *changeRes.UpdateControl.Control.MaturityLevel)
-
- // clear (explicit null)
- var clearRes updateResult
- err = owner.Execute(updateControlQuery, map[string]any{
- "input": map[string]any{
- "id": controlID,
- "maturityLevel": nil,
- },
- }, &clearRes)
- require.NoError(t, err)
- assert.Nil(t, clearRes.UpdateControl.Control.MaturityLevel)
-
- // set again, then omit field on next update -> stays unchanged
- var setAgain updateResult
- err = owner.Execute(updateControlQuery, map[string]any{
- "input": map[string]any{
- "id": controlID,
- "maturityLevel": "MANAGED",
- },
- }, &setAgain)
- require.NoError(t, err)
+ assert.Equal(t, "OPTIMIZING", changeRes.UpdateControl.Control.MaturityLevel)
+ // omit field on next update -> stays unchanged
var omitRes updateResult
err = owner.Execute(updateControlQuery, map[string]any{
"input": map[string]any{
@@ -673,8 +649,7 @@ func TestControl_MaturityLevel(t *testing.T) {
},
}, &omitRes)
require.NoError(t, err)
- require.NotNil(t, omitRes.UpdateControl.Control.MaturityLevel)
- assert.Equal(t, "MANAGED", *omitRes.UpdateControl.Control.MaturityLevel)
+ assert.Equal(t, "OPTIMIZING", omitRes.UpdateControl.Control.MaturityLevel)
})
t.Run("invalid maturityLevel is rejected", func(t *testing.T) {
@@ -686,7 +661,6 @@ func TestControl_MaturityLevel(t *testing.T) {
"name": "Bad maturity",
"description": "bad maturity description",
"bestPractice": true,
- "implemented": "IMPLEMENTED",
"maturityLevel": "BOGUS",
},
}, &res)
@@ -755,12 +729,12 @@ func TestControl_SubResolvers(t *testing.T) {
err = owner.Execute(createControlQuery, map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "name": "SubResolver Test Control",
- "description": "Test description",
- "sectionTitle": "Section 1",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "name": "SubResolver Test Control",
+ "description": "Test description",
+ "sectionTitle": "Section 1",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
}, &controlResult)
require.NoError(t, err)
diff --git a/e2e/console/mapping_test.go b/e2e/console/mapping_test.go
index fa7be2d23..93c1a32da 100644
--- a/e2e/console/mapping_test.go
+++ b/e2e/console/mapping_test.go
@@ -78,12 +78,12 @@ func TestControlMeasureMapping_CreateDelete(t *testing.T) {
}
`, map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "name": "Control for Mapping",
- "description": "Test control for mapping",
- "sectionTitle": "Section 1",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "name": "Control for Mapping",
+ "description": "Test control for mapping",
+ "sectionTitle": "Section 1",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
}, &createControlResult)
require.NoError(t, err)
@@ -358,12 +358,12 @@ func TestControlDocumentMapping_CreateDelete(t *testing.T) {
}
`, map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "name": "Control for Document Mapping",
- "description": "Test control",
- "sectionTitle": "Section 1",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "name": "Control for Document Mapping",
+ "description": "Test control",
+ "sectionTitle": "Section 1",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
}, &createControlResult)
require.NoError(t, err)
@@ -498,12 +498,12 @@ func TestControlAuditMapping_CreateDelete(t *testing.T) {
}
`, map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "name": "Control for Audit Mapping",
- "description": "Test control",
- "sectionTitle": "Section 1",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "name": "Control for Audit Mapping",
+ "description": "Test control",
+ "sectionTitle": "Section 1",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
}, &createControlResult)
require.NoError(t, err)
@@ -636,12 +636,12 @@ func TestControlSnapshotMapping_CreateDelete(t *testing.T) {
}
`, map[string]any{
"input": map[string]any{
- "frameworkId": frameworkID,
- "name": "Control for Snapshot Mapping",
- "description": "Test control",
- "sectionTitle": "Section 1",
- "bestPractice": true,
- "implemented": "IMPLEMENTED",
+ "frameworkId": frameworkID,
+ "name": "Control for Snapshot Mapping",
+ "description": "Test control",
+ "sectionTitle": "Section 1",
+ "bestPractice": true,
+ "maturityLevel": "INITIAL",
},
}, &createControlResult)
require.NoError(t, err)
diff --git a/e2e/console/rbac_test.go b/e2e/console/rbac_test.go
index 99276d7c8..8a64602a9 100644
--- a/e2e/console/rbac_test.go
+++ b/e2e/console/rbac_test.go
@@ -446,7 +446,7 @@ func TestRBAC(t *testing.T) {
client: owner,
query: createControlMutation,
variables: func() map[string]any {
- return map[string]any{"input": map[string]any{"frameworkId": frameworkID, "name": factory.SafeName("Control"), "description": "Test", "sectionTitle": factory.SafeName("Section Owner"), "bestPractice": true, "implemented": "IMPLEMENTED"}}
+ return map[string]any{"input": map[string]any{"frameworkId": frameworkID, "name": factory.SafeName("Control"), "description": "Test", "sectionTitle": factory.SafeName("Section Owner"), "bestPractice": true, "maturityLevel": "INITIAL"}}
},
shouldAllow: true,
},
@@ -456,7 +456,7 @@ func TestRBAC(t *testing.T) {
client: admin,
query: createControlMutation,
variables: func() map[string]any {
- return map[string]any{"input": map[string]any{"frameworkId": frameworkID, "name": factory.SafeName("Control"), "description": "Test", "sectionTitle": factory.SafeName("Section Admin"), "bestPractice": true, "implemented": "IMPLEMENTED"}}
+ return map[string]any{"input": map[string]any{"frameworkId": frameworkID, "name": factory.SafeName("Control"), "description": "Test", "sectionTitle": factory.SafeName("Section Admin"), "bestPractice": true, "maturityLevel": "INITIAL"}}
},
shouldAllow: true,
},
@@ -466,7 +466,7 @@ func TestRBAC(t *testing.T) {
client: viewer,
query: createControlMutation,
variables: func() map[string]any {
- return map[string]any{"input": map[string]any{"frameworkId": frameworkID, "name": factory.SafeName("Control"), "description": "Test", "sectionTitle": factory.SafeName("Section Viewer"), "bestPractice": true, "implemented": "IMPLEMENTED"}}
+ return map[string]any{"input": map[string]any{"frameworkId": frameworkID, "name": factory.SafeName("Control"), "description": "Test", "sectionTitle": factory.SafeName("Section Viewer"), "bestPractice": true, "maturityLevel": "INITIAL"}}
},
shouldAllow: false,
},
diff --git a/e2e/internal/factory/factory.go b/e2e/internal/factory/factory.go
index d279f84a0..feb7dac55 100644
--- a/e2e/internal/factory/factory.go
+++ b/e2e/internal/factory/factory.go
@@ -248,12 +248,12 @@ func CreateControl(c *testutil.Client, frameworkID string, attrs ...Attrs) strin
`
input := map[string]any{
- "frameworkId": frameworkID,
- "name": a.getString("name", SafeName("Control")),
- "description": a.getString("description", "Test control description"),
- "sectionTitle": a.getString("sectionTitle", fmt.Sprintf("Section %s", gofakeit.LetterN(3))),
- "bestPractice": a.getBool("bestPractice", true),
- "implemented": a.getString("implemented", "IMPLEMENTED"),
+ "frameworkId": frameworkID,
+ "name": a.getString("name", SafeName("Control")),
+ "description": a.getString("description", "Test control description"),
+ "sectionTitle": a.getString("sectionTitle", fmt.Sprintf("Section %s", gofakeit.LetterN(3))),
+ "bestPractice": a.getBool("bestPractice", true),
+ "maturityLevel": a.getString("maturityLevel", "INITIAL"),
}
if justification := a.getStringPtr("notImplementedJustification"); justification != nil {
@@ -502,8 +502,8 @@ func (b *ControlBuilder) WithBestPractice(bestPractice bool) *ControlBuilder {
return b
}
-func (b *ControlBuilder) WithImplemented(implemented string) *ControlBuilder {
- b.attrs["implemented"] = implemented
+func (b *ControlBuilder) WithMaturityLevel(maturityLevel string) *ControlBuilder {
+ b.attrs["maturityLevel"] = maturityLevel
return b
}
diff --git a/packages/n8n-node/nodes/Probo/actions/control/get.operation.ts b/packages/n8n-node/nodes/Probo/actions/control/get.operation.ts
index 1be2bf8b3..9c9333ae6 100644
--- a/packages/n8n-node/nodes/Probo/actions/control/get.operation.ts
+++ b/packages/n8n-node/nodes/Probo/actions/control/get.operation.ts
@@ -46,7 +46,6 @@ export async function execute(
sectionTitle
name
description
- implemented
notImplementedJustification
maturityLevel
createdAt
diff --git a/packages/n8n-node/nodes/Probo/actions/control/getAll.operation.ts b/packages/n8n-node/nodes/Probo/actions/control/getAll.operation.ts
index fcd95f2cc..7b58db854 100644
--- a/packages/n8n-node/nodes/Probo/actions/control/getAll.operation.ts
+++ b/packages/n8n-node/nodes/Probo/actions/control/getAll.operation.ts
@@ -81,7 +81,6 @@ export async function execute(
sectionTitle
name
description
- implemented
maturityLevel
createdAt
updatedAt
diff --git a/pkg/cmd/control/create/create.go b/pkg/cmd/control/create/create.go
index cf8b3021e..75fd7017d 100644
--- a/pkg/cmd/control/create/create.go
+++ b/pkg/cmd/control/create/create.go
@@ -33,7 +33,6 @@ mutation($input: CreateControlInput!) {
name
description
bestPractice
- implemented
notImplementedJustification
maturityLevel
}
@@ -60,9 +59,8 @@ type createResponse struct {
Name string `json:"name"`
Description *string `json:"description"`
BestPractice bool `json:"bestPractice"`
- Implemented string `json:"implemented"`
NotImplementedJustification *string `json:"notImplementedJustification"`
- MaturityLevel *string `json:"maturityLevel"`
+ MaturityLevel string `json:"maturityLevel"`
} `json:"node"`
} `json:"controlEdge"`
} `json:"createControl"`
@@ -75,16 +73,15 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
flagName string
flagDescription string
flagBestPractice bool
- flagNotImplemented bool
- flagNotImplementedJustification string
flagMaturityLevel string
+ flagNotImplementedJustification string
)
cmd := &cobra.Command{
Use: "create",
Short: "Create a new control",
Example: ` # Create a control
- prb control create --framework FW_ID --section-title "A.5" --name "Information security policies"`,
+ prb control create --framework FW_ID --section-title "A.5" --name "Information security policies" --maturity-level INITIAL`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := f.Config()
@@ -105,34 +102,26 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
cmdutil.TokenRefreshOption(cfg, host, hc),
)
- implemented := "IMPLEMENTED"
- if flagNotImplemented {
- implemented = "NOT_IMPLEMENTED"
+ if err := cmdutil.ValidateEnum("maturity-level", flagMaturityLevel, maturityLevelValues); err != nil {
+ return err
}
input := map[string]any{
- "frameworkId": flagFramework,
- "sectionTitle": flagSectionTitle,
- "name": flagName,
- "bestPractice": flagBestPractice,
- "implemented": implemented,
+ "frameworkId": flagFramework,
+ "sectionTitle": flagSectionTitle,
+ "name": flagName,
+ "bestPractice": flagBestPractice,
+ "maturityLevel": flagMaturityLevel,
}
if flagDescription != "" {
input["description"] = flagDescription
}
- if flagNotImplemented && flagNotImplementedJustification != "" {
+ if flagMaturityLevel == "NONE" && flagNotImplementedJustification != "" {
input["notImplementedJustification"] = flagNotImplementedJustification
}
- if flagMaturityLevel != "" {
- if err := cmdutil.ValidateEnum("maturity-level", flagMaturityLevel, maturityLevelValues); err != nil {
- return err
- }
- input["maturityLevel"] = flagMaturityLevel
- }
-
data, err := client.Do(
createMutation,
map[string]any{"input": input},
@@ -163,9 +152,8 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
cmd.Flags().StringVar(&flagName, "name", "", "Control name (required)")
cmd.Flags().StringVar(&flagDescription, "description", "", "Control description")
cmd.Flags().BoolVar(&flagBestPractice, "best-practice", false, "Mark as best practice")
- cmd.Flags().BoolVar(&flagNotImplemented, "not-implemented", false, "Mark as not implemented")
- cmd.Flags().StringVar(&flagNotImplementedJustification, "not-implemented-justification", "", "Justification for non-implementation")
- cmd.Flags().StringVar(&flagMaturityLevel, "maturity-level", "", "CMMI maturity level (NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING)")
+ cmd.Flags().StringVar(&flagMaturityLevel, "maturity-level", "INITIAL", "CMMI maturity level (NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING)")
+ cmd.Flags().StringVar(&flagNotImplementedJustification, "not-implemented-justification", "", "Justification when maturity level is NONE")
_ = cmd.MarkFlagRequired("framework")
_ = cmd.MarkFlagRequired("section-title")
diff --git a/pkg/cmd/control/list/list.go b/pkg/cmd/control/list/list.go
index 844563157..e3fd10523 100644
--- a/pkg/cmd/control/list/list.go
+++ b/pkg/cmd/control/list/list.go
@@ -21,6 +21,8 @@ import (
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
+ "go.probo.inc/probo/pkg/coredata"
+ "go.probo.inc/probo/pkg/docgen"
)
const listQuery = `
@@ -37,7 +39,6 @@ query($id: ID!, $first: Int, $after: CursorKey, $orderBy: ControlOrder, $filter:
name
description
bestPractice
- implemented
maturityLevel
}
}
@@ -57,8 +58,7 @@ type control struct {
Name string `json:"name"`
Description *string `json:"description"`
BestPractice bool `json:"bestPractice"`
- Implemented string `json:"implemented"`
- MaturityLevel *string `json:"maturityLevel"`
+ MaturityLevel string `json:"maturityLevel"`
}
func NewCmdList(f *cmdutil.Factory) *cobra.Command {
@@ -167,16 +167,12 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command {
if c.BestPractice {
bp = "Yes"
}
- maturity := "-"
- if c.MaturityLevel != nil {
- maturity = *c.MaturityLevel
- }
rows = append(rows, []string{
c.ID,
c.SectionTitle,
c.Name,
bp,
- maturity,
+ docgen.MaturityLabel(coredata.ControlMaturityLevel(c.MaturityLevel)),
})
}
diff --git a/pkg/cmd/control/update/update.go b/pkg/cmd/control/update/update.go
index 0ca6b8011..983ad6d4b 100644
--- a/pkg/cmd/control/update/update.go
+++ b/pkg/cmd/control/update/update.go
@@ -32,7 +32,6 @@ mutation($input: UpdateControlInput!) {
name
description
bestPractice
- implemented
notImplementedJustification
maturityLevel
}
@@ -48,9 +47,8 @@ type updateResponse struct {
Name string `json:"name"`
Description *string `json:"description"`
BestPractice bool `json:"bestPractice"`
- Implemented string `json:"implemented"`
NotImplementedJustification *string `json:"notImplementedJustification"`
- MaturityLevel *string `json:"maturityLevel"`
+ MaturityLevel string `json:"maturityLevel"`
} `json:"control"`
} `json:"updateControl"`
}
@@ -70,9 +68,8 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
flagName string
flagDescription string
flagBestPractice bool
- flagNotImplemented bool
- flagNotImplementedJustification string
flagMaturityLevel string
+ flagNotImplementedJustification string
)
cmd := &cobra.Command{
@@ -118,12 +115,11 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
if cmd.Flags().Changed("best-practice") {
input["bestPractice"] = flagBestPractice
}
- if cmd.Flags().Changed("not-implemented") {
- if flagNotImplemented {
- input["implemented"] = "NOT_IMPLEMENTED"
- } else {
- input["implemented"] = "IMPLEMENTED"
+ if cmd.Flags().Changed("maturity-level") {
+ if err := cmdutil.ValidateEnum("maturity-level", flagMaturityLevel, maturityLevelValues); err != nil {
+ return err
}
+ input["maturityLevel"] = flagMaturityLevel
}
if cmd.Flags().Changed("not-implemented-justification") {
if flagNotImplementedJustification == "" {
@@ -132,16 +128,6 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
input["notImplementedJustification"] = flagNotImplementedJustification
}
}
- if cmd.Flags().Changed("maturity-level") {
- if flagMaturityLevel == "" {
- input["maturityLevel"] = nil
- } else {
- if err := cmdutil.ValidateEnum("maturity-level", flagMaturityLevel, maturityLevelValues); err != nil {
- return err
- }
- input["maturityLevel"] = flagMaturityLevel
- }
- }
if len(input) == 1 {
return fmt.Errorf("at least one field must be specified for update")
@@ -176,9 +162,8 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
cmd.Flags().StringVar(&flagName, "name", "", "Control name")
cmd.Flags().StringVar(&flagDescription, "description", "", "Control description")
cmd.Flags().BoolVar(&flagBestPractice, "best-practice", false, "Mark as best practice")
- cmd.Flags().BoolVar(&flagNotImplemented, "not-implemented", false, "Mark as not implemented")
- cmd.Flags().StringVar(&flagNotImplementedJustification, "not-implemented-justification", "", "Justification for non-implementation")
- cmd.Flags().StringVar(&flagMaturityLevel, "maturity-level", "", "CMMI maturity level (NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING). Empty string clears the value.")
+ cmd.Flags().StringVar(&flagMaturityLevel, "maturity-level", "", "CMMI maturity level (NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING)")
+ cmd.Flags().StringVar(&flagNotImplementedJustification, "not-implemented-justification", "", "Justification when maturity level is NONE")
return cmd
}
diff --git a/pkg/cmd/control/view/view.go b/pkg/cmd/control/view/view.go
index e1c4099c3..2b977cdbf 100644
--- a/pkg/cmd/control/view/view.go
+++ b/pkg/cmd/control/view/view.go
@@ -22,6 +22,8 @@ import (
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
+ "go.probo.inc/probo/pkg/coredata"
+ "go.probo.inc/probo/pkg/docgen"
)
const viewQuery = `
@@ -34,7 +36,6 @@ query($id: ID!) {
name
description
bestPractice
- implemented
notImplementedJustification
maturityLevel
framework {
@@ -56,9 +57,8 @@ type viewResponse struct {
Name string `json:"name"`
Description *string `json:"description"`
BestPractice bool `json:"bestPractice"`
- Implemented string `json:"implemented"`
NotImplementedJustification *string `json:"notImplementedJustification"`
- MaturityLevel *string `json:"maturityLevel"`
+ MaturityLevel string `json:"maturityLevel"`
Framework struct {
ID string `json:"id"`
Name string `json:"name"`
@@ -144,17 +144,11 @@ func NewCmdView(f *cmdutil.Factory) *cobra.Command {
bp = "Yes"
}
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Best Practice:"), bp)
- _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Implemented:"), c.Implemented)
- if c.Implemented == "NOT_IMPLEMENTED" && c.NotImplementedJustification != nil && *c.NotImplementedJustification != "" {
+ _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Maturity:"), docgen.MaturityLabel(coredata.ControlMaturityLevel(c.MaturityLevel)))
+ if c.MaturityLevel == "NONE" && c.NotImplementedJustification != nil && *c.NotImplementedJustification != "" {
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Justification:"), *c.NotImplementedJustification)
}
- maturity := "Not set"
- if c.MaturityLevel != nil {
- maturity = *c.MaturityLevel
- }
- _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Maturity:"), maturity)
-
_, _ = fmt.Fprintln(out)
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Created:"), cmdutil.FormatTime(c.CreatedAt))
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Updated:"), cmdutil.FormatTime(c.UpdatedAt))
diff --git a/pkg/coredata/control.go b/pkg/coredata/control.go
index 258fa5930..bb37fcce6 100644
--- a/pkg/coredata/control.go
+++ b/pkg/coredata/control.go
@@ -30,18 +30,17 @@ import (
type (
Control struct {
- ID gid.GID `db:"id"`
- OrganizationID gid.GID `db:"organization_id"`
- SectionTitle string `db:"section_title"`
- FrameworkID gid.GID `db:"framework_id"`
- Name string `db:"name"`
- Description *string `db:"description"`
- BestPractice bool `db:"best_practice"`
- Implemented ControlImplementationState `db:"implemented"`
- NotImplementedJustification *string `db:"not_implemented_justification"`
- MaturityLevel *ControlMaturityLevel `db:"maturity_level"`
- CreatedAt time.Time `db:"created_at"`
- UpdatedAt time.Time `db:"updated_at"`
+ ID gid.GID `db:"id"`
+ OrganizationID gid.GID `db:"organization_id"`
+ SectionTitle string `db:"section_title"`
+ FrameworkID gid.GID `db:"framework_id"`
+ Name string `db:"name"`
+ Description *string `db:"description"`
+ BestPractice bool `db:"best_practice"`
+ NotImplementedJustification *string `db:"not_implemented_justification"`
+ MaturityLevel ControlMaturityLevel `db:"maturity_level"`
+ CreatedAt time.Time `db:"created_at"`
+ UpdatedAt time.Time `db:"updated_at"`
}
Controls []*Control
@@ -134,7 +133,6 @@ WITH ctrl AS (
c.name,
c.description,
c.best_practice,
- c.implemented,
c.not_implemented_justification,
c.maturity_level,
c.created_at,
@@ -155,7 +153,6 @@ SELECT
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
@@ -249,7 +246,6 @@ WITH ctrl AS (
c.name,
c.description,
c.best_practice,
- c.implemented,
c.not_implemented_justification,
c.maturity_level,
c.created_at,
@@ -270,7 +266,6 @@ SELECT
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
@@ -370,7 +365,6 @@ WITH ctrl AS (
c.name,
c.description,
c.best_practice,
- c.implemented,
c.not_implemented_justification,
c.maturity_level,
c.created_at,
@@ -397,7 +391,6 @@ SELECT
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
@@ -479,7 +472,6 @@ SELECT
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
@@ -576,7 +568,6 @@ WITH ctrl AS (
c.name,
c.description,
c.best_practice,
- c.implemented,
c.not_implemented_justification,
c.maturity_level,
c.created_at,
@@ -597,7 +588,6 @@ SELECT
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
@@ -646,7 +636,6 @@ SELECT
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
@@ -697,7 +686,6 @@ SELECT
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
@@ -747,7 +735,6 @@ SELECT
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
@@ -794,7 +781,6 @@ INSERT INTO
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
@@ -809,7 +795,6 @@ VALUES (
@name,
@description,
@best_practice,
- @implemented,
@not_implemented_justification,
@maturity_level,
@created_at,
@@ -826,7 +811,6 @@ VALUES (
"name": c.Name,
"description": c.Description,
"best_practice": c.BestPractice,
- "implemented": c.Implemented,
"not_implemented_justification": c.NotImplementedJustification,
"maturity_level": c.MaturityLevel,
"created_at": c.CreatedAt,
@@ -880,7 +864,6 @@ UPDATE controls SET
description = @description,
section_title = @section_title,
best_practice = @best_practice,
- implemented = @implemented,
not_implemented_justification = @not_implemented_justification,
maturity_level = @maturity_level,
updated_at = @updated_at
@@ -895,7 +878,6 @@ WHERE %s
"description": c.Description,
"section_title": c.SectionTitle,
"best_practice": c.BestPractice,
- "implemented": c.Implemented,
"not_implemented_justification": c.NotImplementedJustification,
"maturity_level": c.MaturityLevel,
"updated_at": c.UpdatedAt,
@@ -936,7 +918,6 @@ WITH ctrl AS (
c.name,
c.description,
c.best_practice,
- c.implemented,
c.not_implemented_justification,
c.maturity_level,
c.created_at,
@@ -957,7 +938,6 @@ SELECT
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
@@ -1009,7 +989,6 @@ WITH ctrl AS (
c.name,
c.description,
c.best_practice,
- c.implemented,
c.not_implemented_justification,
c.maturity_level,
c.created_at,
@@ -1030,7 +1009,6 @@ SELECT
name,
description,
best_practice,
- implemented,
not_implemented_justification,
maturity_level,
created_at,
diff --git a/pkg/coredata/control_implementation_state.go b/pkg/coredata/control_implementation_state.go
deleted file mode 100644
index 7d5cbf838..000000000
--- a/pkg/coredata/control_implementation_state.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-package coredata
-
-import (
- "database/sql/driver"
- "fmt"
-)
-
-type (
- ControlImplementationState string
-)
-
-const (
- ControlImplementationStateImplemented ControlImplementationState = "IMPLEMENTED"
- ControlImplementationStateNotImplemented ControlImplementationState = "NOT_IMPLEMENTED"
-)
-
-func (s ControlImplementationState) IsValid() bool {
- switch s {
- case ControlImplementationStateImplemented, ControlImplementationStateNotImplemented:
- return true
- }
- return false
-}
-
-func (s ControlImplementationState) String() string {
- return string(s)
-}
-
-func (s ControlImplementationState) MarshalText() ([]byte, error) {
- return []byte(s.String()), nil
-}
-
-func (s *ControlImplementationState) UnmarshalText(data []byte) error {
- val := ControlImplementationState(data)
- if !val.IsValid() {
- return fmt.Errorf("invalid ControlImplementationState value: %q", string(data))
- }
- *s = val
- return nil
-}
-
-func (s *ControlImplementationState) Scan(value any) error {
- val, ok := value.(string)
- if !ok {
- return fmt.Errorf("invalid scan source for ControlImplementationState, expected string got %T", value)
- }
- return s.UnmarshalText([]byte(val))
-}
-
-func (s ControlImplementationState) Value() (driver.Value, error) {
- return s.String(), nil
-}
diff --git a/pkg/coredata/control_maturity_level.go b/pkg/coredata/control_maturity_level.go
index 6f99c67d4..e5de3949f 100644
--- a/pkg/coredata/control_maturity_level.go
+++ b/pkg/coredata/control_maturity_level.go
@@ -32,6 +32,17 @@ const (
ControlMaturityLevelOptimizing ControlMaturityLevel = "OPTIMIZING"
)
+func ControlMaturityLevels() []ControlMaturityLevel {
+ return []ControlMaturityLevel{
+ ControlMaturityLevelNone,
+ ControlMaturityLevelInitial,
+ ControlMaturityLevelManaged,
+ ControlMaturityLevelDefined,
+ ControlMaturityLevelQuantitativelyManaged,
+ ControlMaturityLevelOptimizing,
+ }
+}
+
func (l ControlMaturityLevel) IsValid() bool {
switch l {
case ControlMaturityLevelNone,
diff --git a/pkg/coredata/migrations/20260419T172808Z.sql b/pkg/coredata/migrations/20260419T172808Z.sql
index bf7078ad4..dff89d867 100644
--- a/pkg/coredata/migrations/20260419T172808Z.sql
+++ b/pkg/coredata/migrations/20260419T172808Z.sql
@@ -12,4 +12,25 @@
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-- PERFORMANCE OF THIS SOFTWARE.
-ALTER TABLE controls ADD COLUMN maturity_level TEXT;
+CREATE TYPE control_maturity_level AS ENUM (
+ 'NONE',
+ 'INITIAL',
+ 'MANAGED',
+ 'DEFINED',
+ 'QUANTITATIVELY_MANAGED',
+ 'OPTIMIZING'
+);
+
+ALTER TABLE controls ADD COLUMN maturity_level control_maturity_level;
+
+UPDATE controls SET maturity_level = CASE
+ WHEN implemented = 'NOT_IMPLEMENTED' THEN 'NONE'::control_maturity_level
+ ELSE 'INITIAL'::control_maturity_level
+END;
+
+ALTER TABLE controls ALTER COLUMN maturity_level SET NOT NULL;
+ALTER TABLE controls ALTER COLUMN implemented DROP NOT NULL;
+
+-- TODO: drop column and type in a future migration
+-- ALTER TABLE controls DROP COLUMN implemented;
+-- DROP TYPE control_implementation_state;
diff --git a/pkg/docgen/generator.go b/pkg/docgen/generator.go
index df5b85531..8e4d56de1 100644
--- a/pkg/docgen/generator.go
+++ b/pkg/docgen/generator.go
@@ -289,9 +289,8 @@ type (
ControlName string
Applicability string
Justification string
- Implemented string
- NotImplJustification string
MaturityLevel string
+ NotImplJustification string
Regulatory string
Contractual string
BestPractice string
@@ -321,11 +320,8 @@ func BoolLabel(v bool) string {
return "No"
}
-func MaturityLabel(l *coredata.ControlMaturityLevel) string {
- if l == nil {
- return "Not set"
- }
- switch *l {
+func MaturityLabel(l coredata.ControlMaturityLevel) string {
+ switch l {
case coredata.ControlMaturityLevelNone:
return "0 - None"
case coredata.ControlMaturityLevelInitial:
diff --git a/pkg/probo/control_service.go b/pkg/probo/control_service.go
index a6feb9981..b0790dac2 100644
--- a/pkg/probo/control_service.go
+++ b/pkg/probo/control_service.go
@@ -38,9 +38,8 @@ type (
Description *string
SectionTitle string
BestPractice bool
- Implemented coredata.ControlImplementationState
+ MaturityLevel coredata.ControlMaturityLevel
NotImplementedJustification *string
- MaturityLevel *coredata.ControlMaturityLevel
}
UpdateControlRequest struct {
@@ -49,9 +48,8 @@ type (
Description **string
SectionTitle *string
BestPractice *bool
- Implemented *coredata.ControlImplementationState
+ MaturityLevel *coredata.ControlMaturityLevel
NotImplementedJustification **string
- MaturityLevel **coredata.ControlMaturityLevel
}
)
@@ -65,30 +63,12 @@ func (ccr *CreateControlRequest) Validate() error {
v.Check(ccr.NotImplementedJustification, "not_implemented_justification", validator.SafeText(ContentMaxLength))
v.Check(
- ccr.Implemented,
- "implemented",
+ ccr.MaturityLevel,
+ "maturity_level",
validator.Required(),
- validator.OneOfSlice([]string{
- string(coredata.ControlImplementationStateImplemented),
- string(coredata.ControlImplementationStateNotImplemented),
- }),
+ validator.OneOfSlice(coredata.ControlMaturityLevels()),
)
- if ccr.MaturityLevel != nil {
- v.Check(
- *ccr.MaturityLevel,
- "maturity_level",
- validator.OneOfSlice([]string{
- string(coredata.ControlMaturityLevelNone),
- string(coredata.ControlMaturityLevelInitial),
- string(coredata.ControlMaturityLevelManaged),
- string(coredata.ControlMaturityLevelDefined),
- string(coredata.ControlMaturityLevelQuantitativelyManaged),
- string(coredata.ControlMaturityLevelOptimizing),
- }),
- )
- }
-
return v.Error()
}
@@ -100,27 +80,12 @@ func (ucr *UpdateControlRequest) Validate() error {
v.Check(ucr.Description, "description", validator.SafeText(ContentMaxLength))
v.Check(ucr.SectionTitle, "section_title", validator.SafeTextNoNewLine(TitleMaxLength))
v.Check(ucr.NotImplementedJustification, "not_implemented_justification", validator.SafeText(ContentMaxLength))
- v.Check(
- ucr.Implemented,
- "implemented",
- validator.OneOfSlice([]string{
- string(coredata.ControlImplementationStateImplemented),
- string(coredata.ControlImplementationStateNotImplemented),
- }),
- )
- if ucr.MaturityLevel != nil && *ucr.MaturityLevel != nil {
+ if ucr.MaturityLevel != nil {
v.Check(
- **ucr.MaturityLevel,
+ *ucr.MaturityLevel,
"maturity_level",
- validator.OneOfSlice([]string{
- string(coredata.ControlMaturityLevelNone),
- string(coredata.ControlMaturityLevelInitial),
- string(coredata.ControlMaturityLevelManaged),
- string(coredata.ControlMaturityLevelDefined),
- string(coredata.ControlMaturityLevelQuantitativelyManaged),
- string(coredata.ControlMaturityLevelOptimizing),
- }),
+ validator.OneOfSlice(coredata.ControlMaturityLevels()),
)
}
@@ -887,7 +852,7 @@ func (s ControlService) Create(
framework := &coredata.Framework{}
notImplementedJustification := req.NotImplementedJustification
- if req.Implemented == coredata.ControlImplementationStateImplemented {
+ if req.MaturityLevel != coredata.ControlMaturityLevelNone {
notImplementedJustification = nil
}
@@ -898,9 +863,8 @@ func (s ControlService) Create(
Description: req.Description,
SectionTitle: req.SectionTitle,
BestPractice: req.BestPractice,
- Implemented: req.Implemented,
- NotImplementedJustification: notImplementedJustification,
MaturityLevel: req.MaturityLevel,
+ NotImplementedJustification: notImplementedJustification,
CreatedAt: now,
UpdatedAt: now,
}
@@ -1007,21 +971,17 @@ func (s ControlService) Update(
control.BestPractice = *req.BestPractice
}
- if req.Implemented != nil {
- control.Implemented = *req.Implemented
- if *req.Implemented == coredata.ControlImplementationStateImplemented {
+ if req.MaturityLevel != nil {
+ control.MaturityLevel = *req.MaturityLevel
+ if *req.MaturityLevel != coredata.ControlMaturityLevelNone {
control.NotImplementedJustification = nil
}
}
- if req.NotImplementedJustification != nil && control.Implemented == coredata.ControlImplementationStateNotImplemented {
+ if req.NotImplementedJustification != nil && control.MaturityLevel == coredata.ControlMaturityLevelNone {
control.NotImplementedJustification = *req.NotImplementedJustification
}
- if req.MaturityLevel != nil {
- control.MaturityLevel = *req.MaturityLevel
- }
-
control.UpdatedAt = time.Now()
return control.Update(ctx, conn, s.svc.scope)
diff --git a/pkg/probo/framework_service.go b/pkg/probo/framework_service.go
index dcfcb33a0..30ff7248e 100644
--- a/pkg/probo/framework_service.go
+++ b/pkg/probo/framework_service.go
@@ -72,7 +72,6 @@ type (
Name string `json:"name"`
Description string `json:"description"`
BestPractice *bool `json:"best_practice,omitempty"`
- Implemented string `json:"implemented,omitempty"`
NotImplementedJustification *string `json:"not_implemented_justification,omitempty"`
MaturityLevel *string `json:"maturity_level,omitempty"`
} `json:"controls"`
@@ -605,21 +604,17 @@ func (s FrameworkService) Import(
if control.BestPractice != nil {
bestPractice = *control.BestPractice
}
- implemented := coredata.ControlImplementationState(control.Implemented)
- if !implemented.IsValid() {
- implemented = coredata.ControlImplementationStateImplemented
- }
- var notImplementedJustification *string
- if implemented == coredata.ControlImplementationStateNotImplemented {
- notImplementedJustification = control.NotImplementedJustification
- }
- var maturityLevel *coredata.ControlMaturityLevel
+ maturityLevel := coredata.ControlMaturityLevelInitial
if control.MaturityLevel != nil {
ml := coredata.ControlMaturityLevel(*control.MaturityLevel)
if ml.IsValid() {
- maturityLevel = &ml
+ maturityLevel = ml
}
}
+ var notImplementedJustification *string
+ if maturityLevel == coredata.ControlMaturityLevelNone {
+ notImplementedJustification = control.NotImplementedJustification
+ }
control := &coredata.Control{
ID: controlID,
FrameworkID: frameworkID,
@@ -628,9 +623,8 @@ func (s FrameworkService) Import(
Name: control.Name,
Description: &description,
BestPractice: bestPractice,
- Implemented: implemented,
- NotImplementedJustification: notImplementedJustification,
MaturityLevel: maturityLevel,
+ NotImplementedJustification: notImplementedJustification,
CreatedAt: now,
UpdatedAt: now,
}
diff --git a/pkg/probo/generated_document_service.go b/pkg/probo/generated_document_service.go
index 80693b5f2..87273cc1a 100644
--- a/pkg/probo/generated_document_service.go
+++ b/pkg/probo/generated_document_service.go
@@ -289,17 +289,8 @@ func (s *GeneratedDocumentService) buildStatementOfApplicabilityDocumentData(
justification = *stmt.Justification
}
- implemented := "-"
- if applicable {
- if control.Implemented == coredata.ControlImplementationStateImplemented {
- implemented = "Yes"
- } else {
- implemented = "No"
- }
- }
-
notImplJustification := "-"
- if applicable && control.Implemented != coredata.ControlImplementationStateImplemented && control.NotImplementedJustification != nil {
+ if applicable && control.MaturityLevel == coredata.ControlMaturityLevelNone && control.NotImplementedJustification != nil {
notImplJustification = *control.NotImplementedJustification
}
@@ -328,9 +319,8 @@ func (s *GeneratedDocumentService) buildStatementOfApplicabilityDocumentData(
ControlName: control.Name,
Applicability: docgen.BoolLabel(applicable),
Justification: justification,
- Implemented: implemented,
- NotImplJustification: notImplJustification,
MaturityLevel: maturityLevel,
+ NotImplJustification: notImplJustification,
Regulatory: regulatory,
Contractual: contractual,
BestPractice: bestPractice,
diff --git a/pkg/probo/templates/statement_of_applicability.json.tmpl b/pkg/probo/templates/statement_of_applicability.json.tmpl
index 88779211a..839497810 100644
--- a/pkg/probo/templates/statement_of_applicability.json.tmpl
+++ b/pkg/probo/templates/statement_of_applicability.json.tmpl
@@ -26,9 +26,8 @@
{ "type": "tableHeader", "attrs": { "colspan": 1, "rowspan": 2, "colwidth": [250] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Control", "marks": [{ "type": "bold" }] }] }] },
{ "type": "tableHeader", "attrs": { "colspan": 1, "rowspan": 2, "colwidth": [70] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Applicability", "marks": [{ "type": "bold" }] }] }] },
{ "type": "tableHeader", "attrs": { "colspan": 1, "rowspan": 2, "colwidth": [130] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Justification for non-applicability", "marks": [{ "type": "bold" }] }] }] },
- { "type": "tableHeader", "attrs": { "colspan": 1, "rowspan": 2, "colwidth": [70] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Implemented", "marks": [{ "type": "bold" }] }] }] },
- { "type": "tableHeader", "attrs": { "colspan": 1, "rowspan": 2, "colwidth": [110] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Justification for non-implementation", "marks": [{ "type": "bold" }] }] }] },
{ "type": "tableHeader", "attrs": { "colspan": 1, "rowspan": 2, "colwidth": [90] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Maturity", "marks": [{ "type": "bold" }] }] }] },
+ { "type": "tableHeader", "attrs": { "colspan": 1, "rowspan": 2, "colwidth": [110] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Justification for non-implementation", "marks": [{ "type": "bold" }] }] }] },
{ "type": "tableHeader", "attrs": { "colspan": 4, "rowspan": 1 }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Justification for inclusion", "marks": [{ "type": "bold" }] }] }] }
]
},
@@ -48,9 +47,8 @@
{ "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [250] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json (printf "[%s] " .ControlSection)}}, "marks": [{ "type": "code" }] }, { "type": "text", "text": {{json .ControlName}} }] }] },
{ "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [70] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .Applicability}} }] }] },
{ "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [130] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .Justification}} }] }] },
- { "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [70] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .Implemented}} }] }] },
- { "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [110] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .NotImplJustification}} }] }] },
{ "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [90] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .MaturityLevel}} }] }] },
+ { "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [110] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .NotImplJustification}} }] }] },
{ "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [60] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .Regulatory}} }] }] },
{ "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [60] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .Contractual}} }] }] },
{ "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [60] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .BestPractice}} }] }] },
@@ -104,28 +102,6 @@
"type": "paragraph",
"content": [{ "type": "text", "text": "Provides the rationale when a control is not applicable. This field is empty for applicable controls." }]
},
- {
- "type": "heading",
- "attrs": { "level": 3 },
- "content": [{ "type": "text", "text": "Implemented" }]
- },
- {
- "type": "bulletList",
- "content": [
- { "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Yes: ", "marks": [{ "type": "bold" }] }, { "type": "text", "text": "The control has been implemented by the organization." }] }] },
- { "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "No: ", "marks": [{ "type": "bold" }] }, { "type": "text", "text": "The control has not been implemented (with justification provided)." }] }] },
- { "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "-: ", "marks": [{ "type": "bold" }] }, { "type": "text", "text": "Not applicable (control is not applicable)." }] }] }
- ]
- },
- {
- "type": "heading",
- "attrs": { "level": 3 },
- "content": [{ "type": "text", "text": "Justification for non-implementation" }]
- },
- {
- "type": "paragraph",
- "content": [{ "type": "text", "text": "Provides the rationale when a control is not implemented. This field is empty for implemented controls or when the control is not applicable." }]
- },
{
"type": "heading",
"attrs": { "level": 3 },
@@ -144,10 +120,18 @@
{ "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "3 - Defined: ", "marks": [{ "type": "bold" }] }, { "type": "text", "text": "documented, standardized and integrated into the organization." }] }] },
{ "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "4 - Quantitatively Managed: ", "marks": [{ "type": "bold" }] }, { "type": "text", "text": "measured, controlled with metrics and statistical objectives." }] }] },
{ "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "5 - Optimizing: ", "marks": [{ "type": "bold" }] }, { "type": "text", "text": "continuous improvement based on quantitative analysis." }] }] },
- { "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Not set: ", "marks": [{ "type": "bold" }] }, { "type": "text", "text": "the maturity level has not yet been assessed." }] }] },
{ "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "-: ", "marks": [{ "type": "bold" }] }, { "type": "text", "text": "Not applicable (control is not applicable)." }] }] }
]
},
+ {
+ "type": "heading",
+ "attrs": { "level": 3 },
+ "content": [{ "type": "text", "text": "Justification for non-implementation" }]
+ },
+ {
+ "type": "paragraph",
+ "content": [{ "type": "text", "text": "Provides the rationale when a control has a maturity level of 0 - None. This field is empty for controls with higher maturity levels or when the control is not applicable." }]
+ },
{
"type": "heading",
"attrs": { "level": 3 },
diff --git a/pkg/server/api/console/v1/control_resolvers.go b/pkg/server/api/console/v1/control_resolvers.go
index f06265b32..4c375e1be 100644
--- a/pkg/server/api/console/v1/control_resolvers.go
+++ b/pkg/server/api/console/v1/control_resolvers.go
@@ -415,9 +415,8 @@ func (r *mutationResolver) CreateControl(ctx context.Context, input types.Create
Description: input.Description,
SectionTitle: input.SectionTitle,
BestPractice: input.BestPractice,
- Implemented: input.Implemented,
- NotImplementedJustification: input.NotImplementedJustification,
MaturityLevel: input.MaturityLevel,
+ NotImplementedJustification: input.NotImplementedJustification,
},
)
if err != nil {
@@ -453,9 +452,8 @@ func (r *mutationResolver) UpdateControl(ctx context.Context, input types.Update
Description: gqlutils.UnwrapOmittable(input.Description),
SectionTitle: input.SectionTitle,
BestPractice: input.BestPractice,
- Implemented: input.Implemented,
+ MaturityLevel: input.MaturityLevel,
NotImplementedJustification: gqlutils.UnwrapOmittable(input.NotImplementedJustification),
- MaturityLevel: gqlutils.UnwrapOmittable(input.MaturityLevel),
},
)
diff --git a/pkg/server/api/console/v1/graphql/control.graphql b/pkg/server/api/console/v1/graphql/control.graphql
index 9936d162c..780ebfa75 100644
--- a/pkg/server/api/console/v1/graphql/control.graphql
+++ b/pkg/server/api/console/v1/graphql/control.graphql
@@ -1,17 +1,3 @@
-enum ControlImplementationState
- @goModel(
- model: "go.probo.inc/probo/pkg/coredata.ControlImplementationState"
- ) {
- IMPLEMENTED
- @goEnum(
- value: "go.probo.inc/probo/pkg/coredata.ControlImplementationStateImplemented"
- )
- NOT_IMPLEMENTED
- @goEnum(
- value: "go.probo.inc/probo/pkg/coredata.ControlImplementationStateNotImplemented"
- )
-}
-
enum ControlMaturityLevel
@goModel(model: "go.probo.inc/probo/pkg/coredata.ControlMaturityLevel") {
NONE
@@ -113,9 +99,8 @@ type Control implements Node {
name: String!
description: String
bestPractice: Boolean!
- implemented: ControlImplementationState!
notImplementedJustification: String
- maturityLevel: ControlMaturityLevel
+ maturityLevel: ControlMaturityLevel!
regulatory: Boolean! @goField(forceResolver: true)
contractual: Boolean! @goField(forceResolver: true)
riskAssessment: Boolean! @goField(forceResolver: true)
@@ -307,9 +292,8 @@ input CreateControlInput {
name: String!
description: String
bestPractice: Boolean!
- implemented: ControlImplementationState!
+ maturityLevel: ControlMaturityLevel!
notImplementedJustification: String
- maturityLevel: ControlMaturityLevel
}
input UpdateControlInput {
@@ -318,9 +302,8 @@ input UpdateControlInput {
name: String
description: String @goField(omittable: true)
bestPractice: Boolean
- implemented: ControlImplementationState
+ maturityLevel: ControlMaturityLevel
notImplementedJustification: String @goField(omittable: true)
- maturityLevel: ControlMaturityLevel @goField(omittable: true)
}
input DeleteControlInput {
diff --git a/pkg/server/api/console/v1/types/control.go b/pkg/server/api/console/v1/types/control.go
index 4049e28a5..ecf943bbf 100644
--- a/pkg/server/api/console/v1/types/control.go
+++ b/pkg/server/api/console/v1/types/control.go
@@ -75,7 +75,6 @@ func NewControl(control *coredata.Control) *Control {
Name: control.Name,
Description: control.Description,
BestPractice: control.BestPractice,
- Implemented: control.Implemented,
NotImplementedJustification: control.NotImplementedJustification,
MaturityLevel: control.MaturityLevel,
CreatedAt: control.CreatedAt,
diff --git a/pkg/server/api/mcp/v1/schema.resolvers.go b/pkg/server/api/mcp/v1/schema.resolvers.go
index 1bcbe91e9..e6cdba84c 100644
--- a/pkg/server/api/mcp/v1/schema.resolvers.go
+++ b/pkg/server/api/mcp/v1/schema.resolvers.go
@@ -1473,12 +1473,6 @@ func (r *Resolver) AddControlTool(ctx context.Context, req *mcp.CallToolRequest,
svc := r.ProboService(ctx, input.FrameworkID)
- var maturityLevel *coredata.ControlMaturityLevel
- if input.MaturityLevel != nil {
- v := coredata.ControlMaturityLevel(*input.MaturityLevel)
- maturityLevel = &v
- }
-
control, err := svc.Controls.Create(
ctx,
probo.CreateControlRequest{
@@ -1487,9 +1481,8 @@ func (r *Resolver) AddControlTool(ctx context.Context, req *mcp.CallToolRequest,
Description: input.Description,
SectionTitle: input.SectionTitle,
BestPractice: input.BestPractice,
- Implemented: coredata.ControlImplementationState(input.Implemented),
+ MaturityLevel: coredata.ControlMaturityLevel(input.MaturityLevel),
NotImplementedJustification: input.NotImplementedJustification,
- MaturityLevel: maturityLevel,
},
)
if err != nil {
@@ -1506,20 +1499,10 @@ func (r *Resolver) UpdateControlTool(ctx context.Context, req *mcp.CallToolReque
svc := r.ProboService(ctx, input.ID)
- var implemented *coredata.ControlImplementationState
- if input.Implemented != nil {
- v := coredata.ControlImplementationState(*input.Implemented)
- implemented = &v
- }
-
- var maturityLevel **coredata.ControlMaturityLevel
- if rawMaturity := UnwrapOmittable(input.MaturityLevel); rawMaturity != nil {
- var inner *coredata.ControlMaturityLevel
- if *rawMaturity != nil {
- v := coredata.ControlMaturityLevel(**rawMaturity)
- inner = &v
- }
- maturityLevel = &inner
+ var maturityLevel *coredata.ControlMaturityLevel
+ if input.MaturityLevel != nil {
+ v := coredata.ControlMaturityLevel(*input.MaturityLevel)
+ maturityLevel = &v
}
control, err := svc.Controls.Update(
@@ -1530,9 +1513,8 @@ func (r *Resolver) UpdateControlTool(ctx context.Context, req *mcp.CallToolReque
Description: UnwrapOmittable(input.Description),
SectionTitle: input.SectionTitle,
BestPractice: input.BestPractice,
- Implemented: implemented,
- NotImplementedJustification: UnwrapOmittable(input.NotImplementedJustification),
MaturityLevel: maturityLevel,
+ NotImplementedJustification: UnwrapOmittable(input.NotImplementedJustification),
},
)
if err != nil {
diff --git a/pkg/server/api/mcp/v1/specification.yaml b/pkg/server/api/mcp/v1/specification.yaml
index 607768d21..e03e1949c 100644
--- a/pkg/server/api/mcp/v1/specification.yaml
+++ b/pkg/server/api/mcp/v1/specification.yaml
@@ -4212,7 +4212,7 @@ components:
- section_title
- name
- best_practice
- - implemented
+ - maturity_level
- created_at
- updated_at
properties:
@@ -4239,23 +4239,16 @@ components:
best_practice:
type: boolean
description: Whether control is a best practice
- implemented:
+ maturity_level:
type: string
- enum: [IMPLEMENTED, NOT_IMPLEMENTED]
- description: Control implementation state
- go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlImplementationState
+ enum: [NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING]
+ description: CMMI 0-5 maturity level of the control
+ go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlMaturityLevel
not_implemented_justification:
type:
- string
- "null"
description: Justification for non-implementation
- maturity_level:
- type:
- - string
- - "null"
- enum: [NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING, null]
- description: CMMI 0-5 maturity level of the control
- go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlMaturityLevel
created_at:
type: string
format: date-time
@@ -4330,7 +4323,7 @@ components:
- section_title
- name
- best_practice
- - implemented
+ - maturity_level
properties:
organization_id:
$ref: "#/components/schemas/GID"
@@ -4350,23 +4343,16 @@ components:
best_practice:
type: boolean
description: Whether control is a best practice
- implemented:
+ maturity_level:
type: string
- enum: [IMPLEMENTED, NOT_IMPLEMENTED]
- description: Control implementation state
- go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlImplementationState
+ enum: [NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING]
+ description: CMMI 0-5 maturity level of the control
+ go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlMaturityLevel
not_implemented_justification:
type:
- string
- "null"
description: Justification for non-implementation
- maturity_level:
- type:
- - string
- - "null"
- enum: [NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING, null]
- description: CMMI 0-5 maturity level of the control
- go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlMaturityLevel
AddControlOutput:
type: object
@@ -4397,21 +4383,15 @@ components:
best_practice:
type: boolean
description: Whether control is a best practice
- implemented:
+ maturity_level:
type: string
- enum: [IMPLEMENTED, NOT_IMPLEMENTED]
- description: Control implementation state
- go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlImplementationState
+ enum: [NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING]
+ description: CMMI 0-5 maturity level of the control
+ go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlMaturityLevel
not_implemented_justification:
type: ["string", "null"]
description: Justification for non-implementation
go.probo.inc/mcpgen/omittable: true
- maturity_level:
- type: ["string", "null"]
- enum: [NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING, null]
- description: CMMI 0-5 maturity level of the control
- go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlMaturityLevel
- go.probo.inc/mcpgen/omittable: true
UpdateControlOutput:
type: object
diff --git a/pkg/server/api/mcp/v1/types/control.go b/pkg/server/api/mcp/v1/types/control.go
index dabcd85b7..4c56e1018 100644
--- a/pkg/server/api/mcp/v1/types/control.go
+++ b/pkg/server/api/mcp/v1/types/control.go
@@ -19,12 +19,6 @@ import (
)
func NewControl(c *coredata.Control) *Control {
- var maturityLevel *string
- if c.MaturityLevel != nil {
- s := string(*c.MaturityLevel)
- maturityLevel = &s
- }
-
return &Control{
ID: c.ID,
OrganizationID: c.OrganizationID,
@@ -33,9 +27,8 @@ func NewControl(c *coredata.Control) *Control {
Name: c.Name,
Description: c.Description,
BestPractice: c.BestPractice,
- Implemented: ControlImplemented(c.Implemented),
NotImplementedJustification: c.NotImplementedJustification,
- MaturityLevel: maturityLevel,
+ MaturityLevel: ControlMaturityLevel(c.MaturityLevel),
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
|