Replace implemented column with CMMI maturity level

Drop the boolean implemented/not-implemented state in favor of a
mandatory CMMI maturity level enum (NONE, INITIAL, MANAGED, DEFINED,
QUANTITATIVELY_MANAGED, OPTIMIZING) stored as a Postgres enum type.

The migration backfills existing rows (NOT_IMPLEMENTED → NONE,
IMPLEMENTED → INITIAL), makes the column NOT NULL, and drops the old
implemented column and its enum type.

- maturityLevel is required on CreateControlInput and non-nullable (!)
  in the GraphQL schema
- CLI displays human-readable CMMI labels instead of raw enum tokens
- SOA table and published document use a single Maturity column in
  place of the old Implemented + Maturity columns
- Remove ControlImplementationState type and all implemented references
  across backend, frontend, CLI, MCP, n8n, and E2E tests

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-04-20 20:48:23 +02:00
parent da91afc2a7
commit e1148f812e
32 changed files with 271 additions and 586 deletions

View File

@@ -19,14 +19,11 @@ import {
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { Option } from "@probo/ui"; import { Option } from "@probo/ui";
export const MATURITY_LEVEL_UNSET = "__unset__" as const;
export function ControlMaturityLevelOptions() { export function ControlMaturityLevelOptions() {
const { __ } = useTranslate(); const { __ } = useTranslate();
return ( return (
<> <>
<Option value={MATURITY_LEVEL_UNSET}>{__("Not set")}</Option>
{controlMaturityLevels.map(level => ( {controlMaturityLevels.map(level => (
<Option key={level} value={level}> <Option key={level} value={level}>
{getControlMaturityLevelLabel(__, level)} {getControlMaturityLevelLabel(__, level)}

View File

@@ -118,7 +118,6 @@ export const frameworkControlNodeQuery = graphql`
sectionTitle sectionTitle
description description
bestPractice bestPractice
implemented
notImplementedJustification notImplementedJustification
maturityLevel maturityLevel
canUpdate: permission(action: "core:control:update") canUpdate: permission(action: "core:control:update")

View File

@@ -383,25 +383,17 @@ export default function FrameworkControlPage({ queryRef }: Props) {
</Badge> </Badge>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm text-txt-secondary">{__("Implemented")}</span> <span className="text-sm text-txt-secondary">{__("Maturity level")}</span>
<Badge variant={control.implemented === "IMPLEMENTED" ? "success" : "warning"} size="sm"> <Badge variant="neutral" size="sm">
{control.implemented === "IMPLEMENTED" ? __("Implemented") : __("Not Implemented")} {getControlMaturityLevelLabel(__, control.maturityLevel ?? "NONE")}
</Badge> </Badge>
</div> </div>
{control.implemented === "NOT_IMPLEMENTED" && control.notImplementedJustification && ( {control.maturityLevel === "NONE" && control.notImplementedJustification && (
<div> <div>
<span className="text-xs text-txt-secondary">{__("Justification for non-implementation")}</span> <span className="text-xs text-txt-secondary">{__("Justification for non-implementation")}</span>
<div className="text-sm mt-0.5 whitespace-pre-wrap">{control.notImplementedJustification}</div> <div className="text-sm mt-0.5 whitespace-pre-wrap">{control.notImplementedJustification}</div>
</div> </div>
)} )}
<div className="flex items-center gap-2">
<span className="text-sm text-txt-secondary">{__("Maturity level")}</span>
<Badge variant="neutral" size="sm">
{control.maturityLevel
? getControlMaturityLevelLabel(__, control.maturityLevel)
: __("Not set")}
</Badge>
</div>
</div> </div>
</Card> </Card>
<div className="mb-4"> <div className="mb-4">

View File

@@ -32,10 +32,7 @@ import { graphql } from "relay-runtime";
import { z } from "zod"; import { z } from "zod";
import type { FrameworkControlDialogFragment$key } from "#/__generated__/core/FrameworkControlDialogFragment.graphql"; import type { FrameworkControlDialogFragment$key } from "#/__generated__/core/FrameworkControlDialogFragment.graphql";
import { import { ControlMaturityLevelOptions } from "#/components/form/ControlMaturityLevelOptions";
ControlMaturityLevelOptions,
MATURITY_LEVEL_UNSET,
} from "#/components/form/ControlMaturityLevelOptions";
import { useFormWithSchema } from "#/hooks/useFormWithSchema"; import { useFormWithSchema } from "#/hooks/useFormWithSchema";
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
@@ -53,7 +50,6 @@ const controlFragment = graphql`
description description
sectionTitle sectionTitle
bestPractice bestPractice
implemented
notImplementedJustification notImplementedJustification
maturityLevel maturityLevel
} }
@@ -89,10 +85,7 @@ const schema = z.object({
description: z.string().optional().nullable(), description: z.string().optional().nullable(),
sectionTitle: z.string(), sectionTitle: z.string(),
bestPractice: z.boolean(), bestPractice: z.boolean(),
implemented: z.enum(["IMPLEMENTED", "NOT_IMPLEMENTED"]),
notImplementedJustification: z.string().optional().nullable(),
maturityLevel: z.enum([ maturityLevel: z.enum([
MATURITY_LEVEL_UNSET,
"NONE", "NONE",
"INITIAL", "INITIAL",
"MANAGED", "MANAGED",
@@ -100,6 +93,7 @@ const schema = z.object({
"QUANTITATIVELY_MANAGED", "QUANTITATIVELY_MANAGED",
"OPTIMIZING", "OPTIMIZING",
]), ]),
notImplementedJustification: z.string().optional().nullable(),
}); });
export function FrameworkControlDialog(props: Props) { export function FrameworkControlDialog(props: Props) {
@@ -124,9 +118,8 @@ export function FrameworkControlDialog(props: Props) {
description: frameworkControl?.description ?? "", description: frameworkControl?.description ?? "",
sectionTitle: frameworkControl?.sectionTitle ?? "", sectionTitle: frameworkControl?.sectionTitle ?? "",
bestPractice: frameworkControl?.bestPractice ?? true, bestPractice: frameworkControl?.bestPractice ?? true,
implemented: frameworkControl?.implemented ?? "IMPLEMENTED", maturityLevel: frameworkControl?.maturityLevel ?? "INITIAL",
notImplementedJustification: frameworkControl?.notImplementedJustification ?? "", notImplementedJustification: frameworkControl?.notImplementedJustification ?? "",
maturityLevel: frameworkControl?.maturityLevel ?? MATURITY_LEVEL_UNSET,
}), }),
[frameworkControl], [frameworkControl],
); );
@@ -141,14 +134,9 @@ export function FrameworkControlDialog(props: Props) {
}, [defaultValues, reset]); }, [defaultValues, reset]);
const bestPracticeValue = watch("bestPractice"); const bestPracticeValue = watch("bestPractice");
const implementedValue = watch("implemented");
const maturityLevelValue = watch("maturityLevel"); const maturityLevelValue = watch("maturityLevel");
const onSubmit = async (data: z.infer<typeof schema>) => { const onSubmit = async (data: z.infer<typeof schema>) => {
const maturityLevel = data.maturityLevel === MATURITY_LEVEL_UNSET
? null
: data.maturityLevel;
if (frameworkControl) { if (frameworkControl) {
await mutate({ await mutate({
variables: { variables: {
@@ -158,9 +146,8 @@ export function FrameworkControlDialog(props: Props) {
description: data.description || null, description: data.description || null,
sectionTitle: data.sectionTitle, sectionTitle: data.sectionTitle,
bestPractice: data.bestPractice, bestPractice: data.bestPractice,
implemented: data.implemented, maturityLevel: data.maturityLevel,
notImplementedJustification: data.implemented === "IMPLEMENTED" ? null : (data.notImplementedJustification || null), notImplementedJustification: data.maturityLevel === "NONE" ? (data.notImplementedJustification || null) : null,
maturityLevel,
}, },
}, },
}); });
@@ -173,9 +160,8 @@ export function FrameworkControlDialog(props: Props) {
description: data.description || null, description: data.description || null,
sectionTitle: data.sectionTitle, sectionTitle: data.sectionTitle,
bestPractice: data.bestPractice ?? true, bestPractice: data.bestPractice ?? true,
implemented: data.implemented ?? "IMPLEMENTED", maturityLevel: data.maturityLevel,
notImplementedJustification: data.implemented === "IMPLEMENTED" ? null : (data.notImplementedJustification || null), notImplementedJustification: data.maturityLevel === "NONE" ? (data.notImplementedJustification || null) : null,
maturityLevel,
}, },
connections: [props.connectionId!], connections: [props.connectionId!],
}, },
@@ -232,23 +218,6 @@ export function FrameworkControlDialog(props: Props) {
/> />
<span className="text-sm">{__("Best Practice")}</span> <span className="text-sm">{__("Best Practice")}</span>
</label> </label>
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox
checked={implementedValue === "IMPLEMENTED"}
onChange={checked =>
setValue("implemented", checked ? "IMPLEMENTED" : "NOT_IMPLEMENTED")}
/>
<span className="text-sm">{__("Implemented")}</span>
</label>
{implementedValue === "NOT_IMPLEMENTED" && (
<Textarea
id="notImplementedJustification"
variant="ghost"
autogrow
placeholder={__("Justification for non-implementation")}
{...register("notImplementedJustification")}
/>
)}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm">{__("Maturity level")}</span> <span className="text-sm">{__("Maturity level")}</span>
<Select <Select
@@ -260,6 +229,15 @@ export function FrameworkControlDialog(props: Props) {
<ControlMaturityLevelOptions /> <ControlMaturityLevelOptions />
</Select> </Select>
</div> </div>
{maturityLevelValue === "NONE" && (
<Textarea
id="notImplementedJustification"
variant="ghost"
autogrow
placeholder={__("Justification for non-implementation")}
{...register("notImplementedJustification")}
/>
)}
</div> </div>
</DialogContent> </DialogContent>
<DialogFooter> <DialogFooter>

View File

@@ -93,7 +93,6 @@ const createApplicabilityStatementMutation = graphql`
sectionTitle sectionTitle
name name
bestPractice bestPractice
implemented
notImplementedJustification notImplementedJustification
regulatory regulatory
contractual contractual

View File

@@ -86,7 +86,6 @@ export const controlsFragment = graphql`
sectionTitle sectionTitle
name name
bestPractice bestPractice
implemented
notImplementedJustification notImplementedJustification
maturityLevel maturityLevel
regulatory regulatory
@@ -151,7 +150,6 @@ export default function StatementOfApplicabilityControlsTab({
applicability: edge.node.applicability, applicability: edge.node.applicability,
justification: edge.node.justification, justification: edge.node.justification,
bestPractice: edge.node.control.bestPractice, bestPractice: edge.node.control.bestPractice,
implemented: edge.node.control.implemented,
notImplementedJustification: edge.node.control.notImplementedJustification, notImplementedJustification: edge.node.control.notImplementedJustification,
maturityLevel: edge.node.control.maturityLevel, maturityLevel: edge.node.control.maturityLevel,
regulatory: edge.node.control.regulatory, regulatory: edge.node.control.regulatory,
@@ -226,7 +224,6 @@ export default function StatementOfApplicabilityControlsTab({
<Th className="w-[9%]">{__("Framework")}</Th> <Th className="w-[9%]">{__("Framework")}</Th>
<Th className="w-[17%]">{__("Control")}</Th> <Th className="w-[17%]">{__("Control")}</Th>
<Th className="w-[12%]">{__("Applicability")}</Th> <Th className="w-[12%]">{__("Applicability")}</Th>
<Th className="w-[12%]">{__("Implemented")}</Th>
<Th className="w-[14%]">{__("Maturity")}</Th> <Th className="w-[14%]">{__("Maturity")}</Th>
<Th className="w-[7%]">{__("Regulatory")}</Th> <Th className="w-[7%]">{__("Regulatory")}</Th>
<Th className="w-[7%]">{__("Contractual")}</Th> <Th className="w-[7%]">{__("Contractual")}</Th>
@@ -241,7 +238,7 @@ export default function StatementOfApplicabilityControlsTab({
{linkedControls.length === 0 && ( {linkedControls.length === 0 && (
<Tr> <Tr>
<Td <Td
colSpan={canUpdate || canDelete ? 10 : 9} colSpan={canUpdate || canDelete ? 9 : 8}
className="text-center text-txt-secondary py-12" className="text-center text-txt-secondary py-12"
> >
{__("No controls linked")} {__("No controls linked")}
@@ -293,12 +290,12 @@ export default function StatementOfApplicabilityControlsTab({
: ( : (
<div className="space-y-1"> <div className="space-y-1">
<Badge <Badge
variant={control.implemented === "IMPLEMENTED" ? "success" : "danger"} variant={control.maturityLevel !== "NONE" ? "success" : "neutral"}
size="sm" size="sm"
> >
{control.implemented === "IMPLEMENTED" ? __("Yes") : __("No")} {getControlMaturityLevelLabel(__, control.maturityLevel)}
</Badge> </Badge>
{control.implemented === "NOT_IMPLEMENTED" && control.notImplementedJustification && ( {control.maturityLevel === "NONE" && control.notImplementedJustification && (
<p className="text-xs text-txt-secondary break-words"> <p className="text-xs text-txt-secondary break-words">
{control.notImplementedJustification} {control.notImplementedJustification}
</p> </p>
@@ -306,17 +303,6 @@ export default function StatementOfApplicabilityControlsTab({
</div> </div>
)} )}
</Td> </Td>
<Td>
{control.applicability === false
? <span className="text-txt-tertiary">-</span>
: (
<Badge variant="neutral" size="sm">
{control.maturityLevel
? getControlMaturityLevelLabel(__, control.maturityLevel)
: __("Not set")}
</Badge>
)}
</Td>
<Td> <Td>
{control.applicability === false {control.applicability === false
? <span className="text-txt-tertiary">-</span> ? <span className="text-txt-tertiary">-</span>

View File

@@ -370,7 +370,7 @@ type snapshotControl struct {
applicability bool applicability bool
justification *string justification *string
bestPractice bool bestPractice bool
implemented string maturityLevel string
notImplementedJustification *string notImplementedJustification *string
hasLegal bool hasLegal bool
hasContractual bool hasContractual bool
@@ -403,7 +403,7 @@ SELECT
stmt.applicability, stmt.applicability,
stmt.justification, stmt.justification,
c.best_practice, c.best_practice,
c.implemented, c.maturity_level,
c.not_implemented_justification, c.not_implemented_justification,
EXISTS ( EXISTS (
SELECT 1 FROM controls_obligations co SELECT 1 FROM controls_obligations co
@@ -442,7 +442,7 @@ ORDER BY f.name, c.section_title;
&sc.applicability, &sc.applicability,
&sc.justification, &sc.justification,
&sc.bestPractice, &sc.bestPractice,
&sc.implemented, &sc.maturityLevel,
&sc.notImplementedJustification, &sc.notImplementedJustification,
&sc.hasLegal, &sc.hasLegal,
&sc.hasContractual, &sc.hasContractual,
@@ -458,17 +458,13 @@ ORDER BY f.name, c.section_title;
justification = *sc.justification justification = *sc.justification
} }
implemented := "-" maturityLevel := "-"
if applicable { if applicable {
if sc.implemented == "IMPLEMENTED" { maturityLevel = docgen.MaturityLabel(coredata.ControlMaturityLevel(sc.maturityLevel))
implemented = "Yes"
} else {
implemented = "No"
}
} }
notImplJustification := "-" notImplJustification := "-"
if applicable && sc.implemented != "IMPLEMENTED" && sc.notImplementedJustification != nil { if applicable && sc.maturityLevel == "NONE" && sc.notImplementedJustification != nil {
notImplJustification = *sc.notImplementedJustification notImplJustification = *sc.notImplementedJustification
} }
@@ -489,7 +485,7 @@ ORDER BY f.name, c.section_title;
ControlName: sc.controlName, ControlName: sc.controlName,
Applicability: docgen.BoolLabel(applicable), Applicability: docgen.BoolLabel(applicable),
Justification: justification, Justification: justification,
Implemented: implemented, MaturityLevel: maturityLevel,
NotImplJustification: notImplJustification, NotImplJustification: notImplJustification,
Regulatory: regulatory, Regulatory: regulatory,
Contractual: contractual, Contractual: contractual,

View File

@@ -64,7 +64,7 @@ func TestControl_Create(t *testing.T) {
"name": "Information Security Policies", "name": "Information Security Policies",
"description": "Policies for information security", "description": "Policies for information security",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, &result) }, &result)
require.NoError(t, err) require.NoError(t, err)
@@ -270,7 +270,7 @@ func TestControl_RequiredFields(t *testing.T) {
"description": "Test", "description": "Test",
"sectionTitle": "Section 1", "sectionTitle": "Section 1",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, },
wantError: true, wantError: true,
@@ -283,7 +283,7 @@ func TestControl_RequiredFields(t *testing.T) {
"description": "Test", "description": "Test",
"sectionTitle": "Section 1", "sectionTitle": "Section 1",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, },
wantError: true, wantError: true,
@@ -296,7 +296,7 @@ func TestControl_RequiredFields(t *testing.T) {
"name": "Test Control", "name": "Test Control",
"description": "Test", "description": "Test",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, },
wantError: true, wantError: true,
@@ -309,7 +309,7 @@ func TestControl_RequiredFields(t *testing.T) {
"name": "Test Control", "name": "Test Control",
"sectionTitle": "Section 1", "sectionTitle": "Section 1",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, },
wantError: true, wantError: true,
@@ -322,13 +322,13 @@ func TestControl_RequiredFields(t *testing.T) {
"name": "Test Control", "name": "Test Control",
"description": "Test", "description": "Test",
"sectionTitle": "Section 1", "sectionTitle": "Section 1",
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, },
wantError: true, wantError: true,
}, },
{ {
name: "Missing implemented should fail", name: "Missing maturityLevel should fail",
variables: map[string]any{ variables: map[string]any{
"input": map[string]any{ "input": map[string]any{
"frameworkId": frameworkID, "frameworkId": frameworkID,
@@ -423,7 +423,7 @@ func TestControl_OmittableDescription(t *testing.T) {
"description": "Initial description", "description": "Initial description",
"sectionTitle": "Section 1", "sectionTitle": "Section 1",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, &createResult) }, &createResult)
require.NoError(t, err) require.NoError(t, err)
@@ -556,7 +556,7 @@ func TestControl_MaturityLevel(t *testing.T) {
ControlEdge struct { ControlEdge struct {
Node struct { Node struct {
ID string `json:"id"` ID string `json:"id"`
MaturityLevel *string `json:"maturityLevel"` MaturityLevel string `json:"maturityLevel"`
} `json:"node"` } `json:"node"`
} `json:"controlEdge"` } `json:"controlEdge"`
} `json:"createControl"` } `json:"createControl"`
@@ -566,25 +566,25 @@ func TestControl_MaturityLevel(t *testing.T) {
UpdateControl struct { UpdateControl struct {
Control struct { Control struct {
ID string `json:"id"` ID string `json:"id"`
MaturityLevel *string `json:"maturityLevel"` MaturityLevel string `json:"maturityLevel"`
} `json:"control"` } `json:"control"`
} `json:"updateControl"` } `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 var res createResult
err := owner.Execute(createControlQuery, map[string]any{ err := owner.Execute(createControlQuery, map[string]any{
"input": map[string]any{ "input": map[string]any{
"frameworkId": frameworkID, "frameworkId": frameworkID,
"sectionTitle": "M.1", "sectionTitle": "M.1",
"name": "Control without maturity", "name": "Control with initial maturity",
"description": "control without maturity description", "description": "control with initial maturity description",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, &res) }, &res)
require.NoError(t, err) 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) { t.Run("create with maturityLevel persists value", func(t *testing.T) {
@@ -596,16 +596,14 @@ func TestControl_MaturityLevel(t *testing.T) {
"name": "Control with maturity", "name": "Control with maturity",
"description": "control with maturity description", "description": "control with maturity description",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED",
"maturityLevel": "DEFINED", "maturityLevel": "DEFINED",
}, },
}, &res) }, &res)
require.NoError(t, err) 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 var created createResult
err := owner.Execute(createControlQuery, map[string]any{ err := owner.Execute(createControlQuery, map[string]any{
"input": map[string]any{ "input": map[string]any{
@@ -614,7 +612,7 @@ func TestControl_MaturityLevel(t *testing.T) {
"name": "Lifecycle control", "name": "Lifecycle control",
"description": "lifecycle control description", "description": "lifecycle control description",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, &created) }, &created)
require.NoError(t, err) require.NoError(t, err)
@@ -629,8 +627,7 @@ func TestControl_MaturityLevel(t *testing.T) {
}, },
}, &setRes) }, &setRes)
require.NoError(t, err) 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 // change
var changeRes updateResult var changeRes updateResult
@@ -641,30 +638,9 @@ func TestControl_MaturityLevel(t *testing.T) {
}, },
}, &changeRes) }, &changeRes)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, changeRes.UpdateControl.Control.MaturityLevel) assert.Equal(t, "OPTIMIZING", 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)
// omit field on next update -> stays unchanged
var omitRes updateResult var omitRes updateResult
err = owner.Execute(updateControlQuery, map[string]any{ err = owner.Execute(updateControlQuery, map[string]any{
"input": map[string]any{ "input": map[string]any{
@@ -673,8 +649,7 @@ func TestControl_MaturityLevel(t *testing.T) {
}, },
}, &omitRes) }, &omitRes)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, omitRes.UpdateControl.Control.MaturityLevel) assert.Equal(t, "OPTIMIZING", omitRes.UpdateControl.Control.MaturityLevel)
assert.Equal(t, "MANAGED", *omitRes.UpdateControl.Control.MaturityLevel)
}) })
t.Run("invalid maturityLevel is rejected", func(t *testing.T) { t.Run("invalid maturityLevel is rejected", func(t *testing.T) {
@@ -686,7 +661,6 @@ func TestControl_MaturityLevel(t *testing.T) {
"name": "Bad maturity", "name": "Bad maturity",
"description": "bad maturity description", "description": "bad maturity description",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED",
"maturityLevel": "BOGUS", "maturityLevel": "BOGUS",
}, },
}, &res) }, &res)
@@ -760,7 +734,7 @@ func TestControl_SubResolvers(t *testing.T) {
"description": "Test description", "description": "Test description",
"sectionTitle": "Section 1", "sectionTitle": "Section 1",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, &controlResult) }, &controlResult)
require.NoError(t, err) require.NoError(t, err)

View File

@@ -83,7 +83,7 @@ func TestControlMeasureMapping_CreateDelete(t *testing.T) {
"description": "Test control for mapping", "description": "Test control for mapping",
"sectionTitle": "Section 1", "sectionTitle": "Section 1",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, &createControlResult) }, &createControlResult)
require.NoError(t, err) require.NoError(t, err)
@@ -363,7 +363,7 @@ func TestControlDocumentMapping_CreateDelete(t *testing.T) {
"description": "Test control", "description": "Test control",
"sectionTitle": "Section 1", "sectionTitle": "Section 1",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, &createControlResult) }, &createControlResult)
require.NoError(t, err) require.NoError(t, err)
@@ -503,7 +503,7 @@ func TestControlAuditMapping_CreateDelete(t *testing.T) {
"description": "Test control", "description": "Test control",
"sectionTitle": "Section 1", "sectionTitle": "Section 1",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, &createControlResult) }, &createControlResult)
require.NoError(t, err) require.NoError(t, err)
@@ -641,7 +641,7 @@ func TestControlSnapshotMapping_CreateDelete(t *testing.T) {
"description": "Test control", "description": "Test control",
"sectionTitle": "Section 1", "sectionTitle": "Section 1",
"bestPractice": true, "bestPractice": true,
"implemented": "IMPLEMENTED", "maturityLevel": "INITIAL",
}, },
}, &createControlResult) }, &createControlResult)
require.NoError(t, err) require.NoError(t, err)

View File

@@ -446,7 +446,7 @@ func TestRBAC(t *testing.T) {
client: owner, client: owner,
query: createControlMutation, query: createControlMutation,
variables: func() map[string]any { 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, shouldAllow: true,
}, },
@@ -456,7 +456,7 @@ func TestRBAC(t *testing.T) {
client: admin, client: admin,
query: createControlMutation, query: createControlMutation,
variables: func() map[string]any { 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, shouldAllow: true,
}, },
@@ -466,7 +466,7 @@ func TestRBAC(t *testing.T) {
client: viewer, client: viewer,
query: createControlMutation, query: createControlMutation,
variables: func() map[string]any { 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, shouldAllow: false,
}, },

View File

@@ -253,7 +253,7 @@ func CreateControl(c *testutil.Client, frameworkID string, attrs ...Attrs) strin
"description": a.getString("description", "Test control description"), "description": a.getString("description", "Test control description"),
"sectionTitle": a.getString("sectionTitle", fmt.Sprintf("Section %s", gofakeit.LetterN(3))), "sectionTitle": a.getString("sectionTitle", fmt.Sprintf("Section %s", gofakeit.LetterN(3))),
"bestPractice": a.getBool("bestPractice", true), "bestPractice": a.getBool("bestPractice", true),
"implemented": a.getString("implemented", "IMPLEMENTED"), "maturityLevel": a.getString("maturityLevel", "INITIAL"),
} }
if justification := a.getStringPtr("notImplementedJustification"); justification != nil { if justification := a.getStringPtr("notImplementedJustification"); justification != nil {
@@ -502,8 +502,8 @@ func (b *ControlBuilder) WithBestPractice(bestPractice bool) *ControlBuilder {
return b return b
} }
func (b *ControlBuilder) WithImplemented(implemented string) *ControlBuilder { func (b *ControlBuilder) WithMaturityLevel(maturityLevel string) *ControlBuilder {
b.attrs["implemented"] = implemented b.attrs["maturityLevel"] = maturityLevel
return b return b
} }

View File

@@ -46,7 +46,6 @@ export async function execute(
sectionTitle sectionTitle
name name
description description
implemented
notImplementedJustification notImplementedJustification
maturityLevel maturityLevel
createdAt createdAt

View File

@@ -81,7 +81,6 @@ export async function execute(
sectionTitle sectionTitle
name name
description description
implemented
maturityLevel maturityLevel
createdAt createdAt
updatedAt updatedAt

View File

@@ -33,7 +33,6 @@ mutation($input: CreateControlInput!) {
name name
description description
bestPractice bestPractice
implemented
notImplementedJustification notImplementedJustification
maturityLevel maturityLevel
} }
@@ -60,9 +59,8 @@ type createResponse struct {
Name string `json:"name"` Name string `json:"name"`
Description *string `json:"description"` Description *string `json:"description"`
BestPractice bool `json:"bestPractice"` BestPractice bool `json:"bestPractice"`
Implemented string `json:"implemented"`
NotImplementedJustification *string `json:"notImplementedJustification"` NotImplementedJustification *string `json:"notImplementedJustification"`
MaturityLevel *string `json:"maturityLevel"` MaturityLevel string `json:"maturityLevel"`
} `json:"node"` } `json:"node"`
} `json:"controlEdge"` } `json:"controlEdge"`
} `json:"createControl"` } `json:"createControl"`
@@ -75,16 +73,15 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
flagName string flagName string
flagDescription string flagDescription string
flagBestPractice bool flagBestPractice bool
flagNotImplemented bool
flagNotImplementedJustification string
flagMaturityLevel string flagMaturityLevel string
flagNotImplementedJustification string
) )
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "create", Use: "create",
Short: "Create a new control", Short: "Create a new control",
Example: ` # Create a 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, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := f.Config() cfg, err := f.Config()
@@ -105,9 +102,8 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
cmdutil.TokenRefreshOption(cfg, host, hc), cmdutil.TokenRefreshOption(cfg, host, hc),
) )
implemented := "IMPLEMENTED" if err := cmdutil.ValidateEnum("maturity-level", flagMaturityLevel, maturityLevelValues); err != nil {
if flagNotImplemented { return err
implemented = "NOT_IMPLEMENTED"
} }
input := map[string]any{ input := map[string]any{
@@ -115,24 +111,17 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
"sectionTitle": flagSectionTitle, "sectionTitle": flagSectionTitle,
"name": flagName, "name": flagName,
"bestPractice": flagBestPractice, "bestPractice": flagBestPractice,
"implemented": implemented, "maturityLevel": flagMaturityLevel,
} }
if flagDescription != "" { if flagDescription != "" {
input["description"] = flagDescription input["description"] = flagDescription
} }
if flagNotImplemented && flagNotImplementedJustification != "" { if flagMaturityLevel == "NONE" && flagNotImplementedJustification != "" {
input["notImplementedJustification"] = 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( data, err := client.Do(
createMutation, createMutation,
map[string]any{"input": input}, 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(&flagName, "name", "", "Control name (required)")
cmd.Flags().StringVar(&flagDescription, "description", "", "Control description") cmd.Flags().StringVar(&flagDescription, "description", "", "Control description")
cmd.Flags().BoolVar(&flagBestPractice, "best-practice", false, "Mark as best practice") 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(&flagMaturityLevel, "maturity-level", "INITIAL", "CMMI maturity level (NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING)")
cmd.Flags().StringVar(&flagNotImplementedJustification, "not-implemented-justification", "", "Justification for non-implementation") cmd.Flags().StringVar(&flagNotImplementedJustification, "not-implemented-justification", "", "Justification when maturity level is NONE")
cmd.Flags().StringVar(&flagMaturityLevel, "maturity-level", "", "CMMI maturity level (NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING)")
_ = cmd.MarkFlagRequired("framework") _ = cmd.MarkFlagRequired("framework")
_ = cmd.MarkFlagRequired("section-title") _ = cmd.MarkFlagRequired("section-title")

View File

@@ -21,6 +21,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api" "go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil" "go.probo.inc/probo/pkg/cmd/cmdutil"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/docgen"
) )
const listQuery = ` const listQuery = `
@@ -37,7 +39,6 @@ query($id: ID!, $first: Int, $after: CursorKey, $orderBy: ControlOrder, $filter:
name name
description description
bestPractice bestPractice
implemented
maturityLevel maturityLevel
} }
} }
@@ -57,8 +58,7 @@ type control struct {
Name string `json:"name"` Name string `json:"name"`
Description *string `json:"description"` Description *string `json:"description"`
BestPractice bool `json:"bestPractice"` BestPractice bool `json:"bestPractice"`
Implemented string `json:"implemented"` MaturityLevel string `json:"maturityLevel"`
MaturityLevel *string `json:"maturityLevel"`
} }
func NewCmdList(f *cmdutil.Factory) *cobra.Command { func NewCmdList(f *cmdutil.Factory) *cobra.Command {
@@ -167,16 +167,12 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command {
if c.BestPractice { if c.BestPractice {
bp = "Yes" bp = "Yes"
} }
maturity := "-"
if c.MaturityLevel != nil {
maturity = *c.MaturityLevel
}
rows = append(rows, []string{ rows = append(rows, []string{
c.ID, c.ID,
c.SectionTitle, c.SectionTitle,
c.Name, c.Name,
bp, bp,
maturity, docgen.MaturityLabel(coredata.ControlMaturityLevel(c.MaturityLevel)),
}) })
} }

View File

@@ -32,7 +32,6 @@ mutation($input: UpdateControlInput!) {
name name
description description
bestPractice bestPractice
implemented
notImplementedJustification notImplementedJustification
maturityLevel maturityLevel
} }
@@ -48,9 +47,8 @@ type updateResponse struct {
Name string `json:"name"` Name string `json:"name"`
Description *string `json:"description"` Description *string `json:"description"`
BestPractice bool `json:"bestPractice"` BestPractice bool `json:"bestPractice"`
Implemented string `json:"implemented"`
NotImplementedJustification *string `json:"notImplementedJustification"` NotImplementedJustification *string `json:"notImplementedJustification"`
MaturityLevel *string `json:"maturityLevel"` MaturityLevel string `json:"maturityLevel"`
} `json:"control"` } `json:"control"`
} `json:"updateControl"` } `json:"updateControl"`
} }
@@ -70,9 +68,8 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
flagName string flagName string
flagDescription string flagDescription string
flagBestPractice bool flagBestPractice bool
flagNotImplemented bool
flagNotImplementedJustification string
flagMaturityLevel string flagMaturityLevel string
flagNotImplementedJustification string
) )
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -118,12 +115,11 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
if cmd.Flags().Changed("best-practice") { if cmd.Flags().Changed("best-practice") {
input["bestPractice"] = flagBestPractice input["bestPractice"] = flagBestPractice
} }
if cmd.Flags().Changed("not-implemented") { if cmd.Flags().Changed("maturity-level") {
if flagNotImplemented { if err := cmdutil.ValidateEnum("maturity-level", flagMaturityLevel, maturityLevelValues); err != nil {
input["implemented"] = "NOT_IMPLEMENTED" return err
} else {
input["implemented"] = "IMPLEMENTED"
} }
input["maturityLevel"] = flagMaturityLevel
} }
if cmd.Flags().Changed("not-implemented-justification") { if cmd.Flags().Changed("not-implemented-justification") {
if flagNotImplementedJustification == "" { if flagNotImplementedJustification == "" {
@@ -132,16 +128,6 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
input["notImplementedJustification"] = flagNotImplementedJustification 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 { if len(input) == 1 {
return fmt.Errorf("at least one field must be specified for update") 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(&flagName, "name", "", "Control name")
cmd.Flags().StringVar(&flagDescription, "description", "", "Control description") cmd.Flags().StringVar(&flagDescription, "description", "", "Control description")
cmd.Flags().BoolVar(&flagBestPractice, "best-practice", false, "Mark as best practice") 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(&flagMaturityLevel, "maturity-level", "", "CMMI maturity level (NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING)")
cmd.Flags().StringVar(&flagNotImplementedJustification, "not-implemented-justification", "", "Justification for non-implementation") cmd.Flags().StringVar(&flagNotImplementedJustification, "not-implemented-justification", "", "Justification when maturity level is NONE")
cmd.Flags().StringVar(&flagMaturityLevel, "maturity-level", "", "CMMI maturity level (NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING). Empty string clears the value.")
return cmd return cmd
} }

View File

@@ -22,6 +22,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api" "go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil" "go.probo.inc/probo/pkg/cmd/cmdutil"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/docgen"
) )
const viewQuery = ` const viewQuery = `
@@ -34,7 +36,6 @@ query($id: ID!) {
name name
description description
bestPractice bestPractice
implemented
notImplementedJustification notImplementedJustification
maturityLevel maturityLevel
framework { framework {
@@ -56,9 +57,8 @@ type viewResponse struct {
Name string `json:"name"` Name string `json:"name"`
Description *string `json:"description"` Description *string `json:"description"`
BestPractice bool `json:"bestPractice"` BestPractice bool `json:"bestPractice"`
Implemented string `json:"implemented"`
NotImplementedJustification *string `json:"notImplementedJustification"` NotImplementedJustification *string `json:"notImplementedJustification"`
MaturityLevel *string `json:"maturityLevel"` MaturityLevel string `json:"maturityLevel"`
Framework struct { Framework struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@@ -144,17 +144,11 @@ func NewCmdView(f *cmdutil.Factory) *cobra.Command {
bp = "Yes" bp = "Yes"
} }
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Best Practice:"), bp) _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Best Practice:"), bp)
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Implemented:"), c.Implemented) _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Maturity:"), docgen.MaturityLabel(coredata.ControlMaturityLevel(c.MaturityLevel)))
if c.Implemented == "NOT_IMPLEMENTED" && c.NotImplementedJustification != nil && *c.NotImplementedJustification != "" { if c.MaturityLevel == "NONE" && c.NotImplementedJustification != nil && *c.NotImplementedJustification != "" {
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Justification:"), *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.Fprintln(out)
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Created:"), cmdutil.FormatTime(c.CreatedAt)) _, _ = 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)) _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Updated:"), cmdutil.FormatTime(c.UpdatedAt))

View File

@@ -37,9 +37,8 @@ type (
Name string `db:"name"` Name string `db:"name"`
Description *string `db:"description"` Description *string `db:"description"`
BestPractice bool `db:"best_practice"` BestPractice bool `db:"best_practice"`
Implemented ControlImplementationState `db:"implemented"`
NotImplementedJustification *string `db:"not_implemented_justification"` NotImplementedJustification *string `db:"not_implemented_justification"`
MaturityLevel *ControlMaturityLevel `db:"maturity_level"` MaturityLevel ControlMaturityLevel `db:"maturity_level"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
} }
@@ -134,7 +133,6 @@ WITH ctrl AS (
c.name, c.name,
c.description, c.description,
c.best_practice, c.best_practice,
c.implemented,
c.not_implemented_justification, c.not_implemented_justification,
c.maturity_level, c.maturity_level,
c.created_at, c.created_at,
@@ -155,7 +153,6 @@ SELECT
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,
@@ -249,7 +246,6 @@ WITH ctrl AS (
c.name, c.name,
c.description, c.description,
c.best_practice, c.best_practice,
c.implemented,
c.not_implemented_justification, c.not_implemented_justification,
c.maturity_level, c.maturity_level,
c.created_at, c.created_at,
@@ -270,7 +266,6 @@ SELECT
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,
@@ -370,7 +365,6 @@ WITH ctrl AS (
c.name, c.name,
c.description, c.description,
c.best_practice, c.best_practice,
c.implemented,
c.not_implemented_justification, c.not_implemented_justification,
c.maturity_level, c.maturity_level,
c.created_at, c.created_at,
@@ -397,7 +391,6 @@ SELECT
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,
@@ -479,7 +472,6 @@ SELECT
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,
@@ -576,7 +568,6 @@ WITH ctrl AS (
c.name, c.name,
c.description, c.description,
c.best_practice, c.best_practice,
c.implemented,
c.not_implemented_justification, c.not_implemented_justification,
c.maturity_level, c.maturity_level,
c.created_at, c.created_at,
@@ -597,7 +588,6 @@ SELECT
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,
@@ -646,7 +636,6 @@ SELECT
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,
@@ -697,7 +686,6 @@ SELECT
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,
@@ -747,7 +735,6 @@ SELECT
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,
@@ -794,7 +781,6 @@ INSERT INTO
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,
@@ -809,7 +795,6 @@ VALUES (
@name, @name,
@description, @description,
@best_practice, @best_practice,
@implemented,
@not_implemented_justification, @not_implemented_justification,
@maturity_level, @maturity_level,
@created_at, @created_at,
@@ -826,7 +811,6 @@ VALUES (
"name": c.Name, "name": c.Name,
"description": c.Description, "description": c.Description,
"best_practice": c.BestPractice, "best_practice": c.BestPractice,
"implemented": c.Implemented,
"not_implemented_justification": c.NotImplementedJustification, "not_implemented_justification": c.NotImplementedJustification,
"maturity_level": c.MaturityLevel, "maturity_level": c.MaturityLevel,
"created_at": c.CreatedAt, "created_at": c.CreatedAt,
@@ -880,7 +864,6 @@ UPDATE controls SET
description = @description, description = @description,
section_title = @section_title, section_title = @section_title,
best_practice = @best_practice, best_practice = @best_practice,
implemented = @implemented,
not_implemented_justification = @not_implemented_justification, not_implemented_justification = @not_implemented_justification,
maturity_level = @maturity_level, maturity_level = @maturity_level,
updated_at = @updated_at updated_at = @updated_at
@@ -895,7 +878,6 @@ WHERE %s
"description": c.Description, "description": c.Description,
"section_title": c.SectionTitle, "section_title": c.SectionTitle,
"best_practice": c.BestPractice, "best_practice": c.BestPractice,
"implemented": c.Implemented,
"not_implemented_justification": c.NotImplementedJustification, "not_implemented_justification": c.NotImplementedJustification,
"maturity_level": c.MaturityLevel, "maturity_level": c.MaturityLevel,
"updated_at": c.UpdatedAt, "updated_at": c.UpdatedAt,
@@ -936,7 +918,6 @@ WITH ctrl AS (
c.name, c.name,
c.description, c.description,
c.best_practice, c.best_practice,
c.implemented,
c.not_implemented_justification, c.not_implemented_justification,
c.maturity_level, c.maturity_level,
c.created_at, c.created_at,
@@ -957,7 +938,6 @@ SELECT
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,
@@ -1009,7 +989,6 @@ WITH ctrl AS (
c.name, c.name,
c.description, c.description,
c.best_practice, c.best_practice,
c.implemented,
c.not_implemented_justification, c.not_implemented_justification,
c.maturity_level, c.maturity_level,
c.created_at, c.created_at,
@@ -1030,7 +1009,6 @@ SELECT
name, name,
description, description,
best_practice, best_practice,
implemented,
not_implemented_justification, not_implemented_justification,
maturity_level, maturity_level,
created_at, created_at,

View File

@@ -1,66 +0,0 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// 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
}

View File

@@ -32,6 +32,17 @@ const (
ControlMaturityLevelOptimizing ControlMaturityLevel = "OPTIMIZING" ControlMaturityLevelOptimizing ControlMaturityLevel = "OPTIMIZING"
) )
func ControlMaturityLevels() []ControlMaturityLevel {
return []ControlMaturityLevel{
ControlMaturityLevelNone,
ControlMaturityLevelInitial,
ControlMaturityLevelManaged,
ControlMaturityLevelDefined,
ControlMaturityLevelQuantitativelyManaged,
ControlMaturityLevelOptimizing,
}
}
func (l ControlMaturityLevel) IsValid() bool { func (l ControlMaturityLevel) IsValid() bool {
switch l { switch l {
case ControlMaturityLevelNone, case ControlMaturityLevelNone,

View File

@@ -12,4 +12,25 @@
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-- PERFORMANCE OF THIS SOFTWARE. -- 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;

View File

@@ -289,9 +289,8 @@ type (
ControlName string ControlName string
Applicability string Applicability string
Justification string Justification string
Implemented string
NotImplJustification string
MaturityLevel string MaturityLevel string
NotImplJustification string
Regulatory string Regulatory string
Contractual string Contractual string
BestPractice string BestPractice string
@@ -321,11 +320,8 @@ func BoolLabel(v bool) string {
return "No" return "No"
} }
func MaturityLabel(l *coredata.ControlMaturityLevel) string { func MaturityLabel(l coredata.ControlMaturityLevel) string {
if l == nil { switch l {
return "Not set"
}
switch *l {
case coredata.ControlMaturityLevelNone: case coredata.ControlMaturityLevelNone:
return "0 - None" return "0 - None"
case coredata.ControlMaturityLevelInitial: case coredata.ControlMaturityLevelInitial:

View File

@@ -38,9 +38,8 @@ type (
Description *string Description *string
SectionTitle string SectionTitle string
BestPractice bool BestPractice bool
Implemented coredata.ControlImplementationState MaturityLevel coredata.ControlMaturityLevel
NotImplementedJustification *string NotImplementedJustification *string
MaturityLevel *coredata.ControlMaturityLevel
} }
UpdateControlRequest struct { UpdateControlRequest struct {
@@ -49,9 +48,8 @@ type (
Description **string Description **string
SectionTitle *string SectionTitle *string
BestPractice *bool BestPractice *bool
Implemented *coredata.ControlImplementationState MaturityLevel *coredata.ControlMaturityLevel
NotImplementedJustification **string NotImplementedJustification **string
MaturityLevel **coredata.ControlMaturityLevel
} }
) )
@@ -65,29 +63,11 @@ func (ccr *CreateControlRequest) Validate() error {
v.Check(ccr.NotImplementedJustification, "not_implemented_justification", validator.SafeText(ContentMaxLength)) v.Check(ccr.NotImplementedJustification, "not_implemented_justification", validator.SafeText(ContentMaxLength))
v.Check( v.Check(
ccr.Implemented, ccr.MaturityLevel,
"implemented",
validator.Required(),
validator.OneOfSlice([]string{
string(coredata.ControlImplementationStateImplemented),
string(coredata.ControlImplementationStateNotImplemented),
}),
)
if ccr.MaturityLevel != nil {
v.Check(
*ccr.MaturityLevel,
"maturity_level", "maturity_level",
validator.OneOfSlice([]string{ validator.Required(),
string(coredata.ControlMaturityLevelNone), validator.OneOfSlice(coredata.ControlMaturityLevels()),
string(coredata.ControlMaturityLevelInitial),
string(coredata.ControlMaturityLevelManaged),
string(coredata.ControlMaturityLevelDefined),
string(coredata.ControlMaturityLevelQuantitativelyManaged),
string(coredata.ControlMaturityLevelOptimizing),
}),
) )
}
return v.Error() return v.Error()
} }
@@ -100,27 +80,12 @@ func (ucr *UpdateControlRequest) Validate() error {
v.Check(ucr.Description, "description", validator.SafeText(ContentMaxLength)) v.Check(ucr.Description, "description", validator.SafeText(ContentMaxLength))
v.Check(ucr.SectionTitle, "section_title", validator.SafeTextNoNewLine(TitleMaxLength)) v.Check(ucr.SectionTitle, "section_title", validator.SafeTextNoNewLine(TitleMaxLength))
v.Check(ucr.NotImplementedJustification, "not_implemented_justification", validator.SafeText(ContentMaxLength)) 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( v.Check(
**ucr.MaturityLevel, *ucr.MaturityLevel,
"maturity_level", "maturity_level",
validator.OneOfSlice([]string{ validator.OneOfSlice(coredata.ControlMaturityLevels()),
string(coredata.ControlMaturityLevelNone),
string(coredata.ControlMaturityLevelInitial),
string(coredata.ControlMaturityLevelManaged),
string(coredata.ControlMaturityLevelDefined),
string(coredata.ControlMaturityLevelQuantitativelyManaged),
string(coredata.ControlMaturityLevelOptimizing),
}),
) )
} }
@@ -887,7 +852,7 @@ func (s ControlService) Create(
framework := &coredata.Framework{} framework := &coredata.Framework{}
notImplementedJustification := req.NotImplementedJustification notImplementedJustification := req.NotImplementedJustification
if req.Implemented == coredata.ControlImplementationStateImplemented { if req.MaturityLevel != coredata.ControlMaturityLevelNone {
notImplementedJustification = nil notImplementedJustification = nil
} }
@@ -898,9 +863,8 @@ func (s ControlService) Create(
Description: req.Description, Description: req.Description,
SectionTitle: req.SectionTitle, SectionTitle: req.SectionTitle,
BestPractice: req.BestPractice, BestPractice: req.BestPractice,
Implemented: req.Implemented,
NotImplementedJustification: notImplementedJustification,
MaturityLevel: req.MaturityLevel, MaturityLevel: req.MaturityLevel,
NotImplementedJustification: notImplementedJustification,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
@@ -1007,21 +971,17 @@ func (s ControlService) Update(
control.BestPractice = *req.BestPractice control.BestPractice = *req.BestPractice
} }
if req.Implemented != nil { if req.MaturityLevel != nil {
control.Implemented = *req.Implemented control.MaturityLevel = *req.MaturityLevel
if *req.Implemented == coredata.ControlImplementationStateImplemented { if *req.MaturityLevel != coredata.ControlMaturityLevelNone {
control.NotImplementedJustification = nil control.NotImplementedJustification = nil
} }
} }
if req.NotImplementedJustification != nil && control.Implemented == coredata.ControlImplementationStateNotImplemented { if req.NotImplementedJustification != nil && control.MaturityLevel == coredata.ControlMaturityLevelNone {
control.NotImplementedJustification = *req.NotImplementedJustification control.NotImplementedJustification = *req.NotImplementedJustification
} }
if req.MaturityLevel != nil {
control.MaturityLevel = *req.MaturityLevel
}
control.UpdatedAt = time.Now() control.UpdatedAt = time.Now()
return control.Update(ctx, conn, s.svc.scope) return control.Update(ctx, conn, s.svc.scope)

View File

@@ -72,7 +72,6 @@ type (
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
BestPractice *bool `json:"best_practice,omitempty"` BestPractice *bool `json:"best_practice,omitempty"`
Implemented string `json:"implemented,omitempty"`
NotImplementedJustification *string `json:"not_implemented_justification,omitempty"` NotImplementedJustification *string `json:"not_implemented_justification,omitempty"`
MaturityLevel *string `json:"maturity_level,omitempty"` MaturityLevel *string `json:"maturity_level,omitempty"`
} `json:"controls"` } `json:"controls"`
@@ -605,21 +604,17 @@ func (s FrameworkService) Import(
if control.BestPractice != nil { if control.BestPractice != nil {
bestPractice = *control.BestPractice bestPractice = *control.BestPractice
} }
implemented := coredata.ControlImplementationState(control.Implemented) maturityLevel := coredata.ControlMaturityLevelInitial
if !implemented.IsValid() {
implemented = coredata.ControlImplementationStateImplemented
}
var notImplementedJustification *string
if implemented == coredata.ControlImplementationStateNotImplemented {
notImplementedJustification = control.NotImplementedJustification
}
var maturityLevel *coredata.ControlMaturityLevel
if control.MaturityLevel != nil { if control.MaturityLevel != nil {
ml := coredata.ControlMaturityLevel(*control.MaturityLevel) ml := coredata.ControlMaturityLevel(*control.MaturityLevel)
if ml.IsValid() { if ml.IsValid() {
maturityLevel = &ml maturityLevel = ml
} }
} }
var notImplementedJustification *string
if maturityLevel == coredata.ControlMaturityLevelNone {
notImplementedJustification = control.NotImplementedJustification
}
control := &coredata.Control{ control := &coredata.Control{
ID: controlID, ID: controlID,
FrameworkID: frameworkID, FrameworkID: frameworkID,
@@ -628,9 +623,8 @@ func (s FrameworkService) Import(
Name: control.Name, Name: control.Name,
Description: &description, Description: &description,
BestPractice: bestPractice, BestPractice: bestPractice,
Implemented: implemented,
NotImplementedJustification: notImplementedJustification,
MaturityLevel: maturityLevel, MaturityLevel: maturityLevel,
NotImplementedJustification: notImplementedJustification,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }

View File

@@ -289,17 +289,8 @@ func (s *GeneratedDocumentService) buildStatementOfApplicabilityDocumentData(
justification = *stmt.Justification justification = *stmt.Justification
} }
implemented := "-"
if applicable {
if control.Implemented == coredata.ControlImplementationStateImplemented {
implemented = "Yes"
} else {
implemented = "No"
}
}
notImplJustification := "-" notImplJustification := "-"
if applicable && control.Implemented != coredata.ControlImplementationStateImplemented && control.NotImplementedJustification != nil { if applicable && control.MaturityLevel == coredata.ControlMaturityLevelNone && control.NotImplementedJustification != nil {
notImplJustification = *control.NotImplementedJustification notImplJustification = *control.NotImplementedJustification
} }
@@ -328,9 +319,8 @@ func (s *GeneratedDocumentService) buildStatementOfApplicabilityDocumentData(
ControlName: control.Name, ControlName: control.Name,
Applicability: docgen.BoolLabel(applicable), Applicability: docgen.BoolLabel(applicable),
Justification: justification, Justification: justification,
Implemented: implemented,
NotImplJustification: notImplJustification,
MaturityLevel: maturityLevel, MaturityLevel: maturityLevel,
NotImplJustification: notImplJustification,
Regulatory: regulatory, Regulatory: regulatory,
Contractual: contractual, Contractual: contractual,
BestPractice: bestPractice, BestPractice: bestPractice,

View File

@@ -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": [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": [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": [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": [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" }] }] }] } { "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": [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": [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": [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": [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 .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 .Contractual}} }] }] },
{ "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [60] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .BestPractice}} }] }] }, { "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": [60] }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": {{json .BestPractice}} }] }] },
@@ -104,28 +102,6 @@
"type": "paragraph", "type": "paragraph",
"content": [{ "type": "text", "text": "Provides the rationale when a control is not applicable. This field is empty for applicable controls." }] "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", "type": "heading",
"attrs": { "level": 3 }, "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": "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": "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": "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": "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", "type": "heading",
"attrs": { "level": 3 }, "attrs": { "level": 3 },

View File

@@ -415,9 +415,8 @@ func (r *mutationResolver) CreateControl(ctx context.Context, input types.Create
Description: input.Description, Description: input.Description,
SectionTitle: input.SectionTitle, SectionTitle: input.SectionTitle,
BestPractice: input.BestPractice, BestPractice: input.BestPractice,
Implemented: input.Implemented,
NotImplementedJustification: input.NotImplementedJustification,
MaturityLevel: input.MaturityLevel, MaturityLevel: input.MaturityLevel,
NotImplementedJustification: input.NotImplementedJustification,
}, },
) )
if err != nil { if err != nil {
@@ -453,9 +452,8 @@ func (r *mutationResolver) UpdateControl(ctx context.Context, input types.Update
Description: gqlutils.UnwrapOmittable(input.Description), Description: gqlutils.UnwrapOmittable(input.Description),
SectionTitle: input.SectionTitle, SectionTitle: input.SectionTitle,
BestPractice: input.BestPractice, BestPractice: input.BestPractice,
Implemented: input.Implemented, MaturityLevel: input.MaturityLevel,
NotImplementedJustification: gqlutils.UnwrapOmittable(input.NotImplementedJustification), NotImplementedJustification: gqlutils.UnwrapOmittable(input.NotImplementedJustification),
MaturityLevel: gqlutils.UnwrapOmittable(input.MaturityLevel),
}, },
) )

View File

@@ -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 enum ControlMaturityLevel
@goModel(model: "go.probo.inc/probo/pkg/coredata.ControlMaturityLevel") { @goModel(model: "go.probo.inc/probo/pkg/coredata.ControlMaturityLevel") {
NONE NONE
@@ -113,9 +99,8 @@ type Control implements Node {
name: String! name: String!
description: String description: String
bestPractice: Boolean! bestPractice: Boolean!
implemented: ControlImplementationState!
notImplementedJustification: String notImplementedJustification: String
maturityLevel: ControlMaturityLevel maturityLevel: ControlMaturityLevel!
regulatory: Boolean! @goField(forceResolver: true) regulatory: Boolean! @goField(forceResolver: true)
contractual: Boolean! @goField(forceResolver: true) contractual: Boolean! @goField(forceResolver: true)
riskAssessment: Boolean! @goField(forceResolver: true) riskAssessment: Boolean! @goField(forceResolver: true)
@@ -307,9 +292,8 @@ input CreateControlInput {
name: String! name: String!
description: String description: String
bestPractice: Boolean! bestPractice: Boolean!
implemented: ControlImplementationState! maturityLevel: ControlMaturityLevel!
notImplementedJustification: String notImplementedJustification: String
maturityLevel: ControlMaturityLevel
} }
input UpdateControlInput { input UpdateControlInput {
@@ -318,9 +302,8 @@ input UpdateControlInput {
name: String name: String
description: String @goField(omittable: true) description: String @goField(omittable: true)
bestPractice: Boolean bestPractice: Boolean
implemented: ControlImplementationState maturityLevel: ControlMaturityLevel
notImplementedJustification: String @goField(omittable: true) notImplementedJustification: String @goField(omittable: true)
maturityLevel: ControlMaturityLevel @goField(omittable: true)
} }
input DeleteControlInput { input DeleteControlInput {

View File

@@ -75,7 +75,6 @@ func NewControl(control *coredata.Control) *Control {
Name: control.Name, Name: control.Name,
Description: control.Description, Description: control.Description,
BestPractice: control.BestPractice, BestPractice: control.BestPractice,
Implemented: control.Implemented,
NotImplementedJustification: control.NotImplementedJustification, NotImplementedJustification: control.NotImplementedJustification,
MaturityLevel: control.MaturityLevel, MaturityLevel: control.MaturityLevel,
CreatedAt: control.CreatedAt, CreatedAt: control.CreatedAt,

View File

@@ -1473,12 +1473,6 @@ func (r *Resolver) AddControlTool(ctx context.Context, req *mcp.CallToolRequest,
svc := r.ProboService(ctx, input.FrameworkID) 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( control, err := svc.Controls.Create(
ctx, ctx,
probo.CreateControlRequest{ probo.CreateControlRequest{
@@ -1487,9 +1481,8 @@ func (r *Resolver) AddControlTool(ctx context.Context, req *mcp.CallToolRequest,
Description: input.Description, Description: input.Description,
SectionTitle: input.SectionTitle, SectionTitle: input.SectionTitle,
BestPractice: input.BestPractice, BestPractice: input.BestPractice,
Implemented: coredata.ControlImplementationState(input.Implemented), MaturityLevel: coredata.ControlMaturityLevel(input.MaturityLevel),
NotImplementedJustification: input.NotImplementedJustification, NotImplementedJustification: input.NotImplementedJustification,
MaturityLevel: maturityLevel,
}, },
) )
if err != nil { if err != nil {
@@ -1506,20 +1499,10 @@ func (r *Resolver) UpdateControlTool(ctx context.Context, req *mcp.CallToolReque
svc := r.ProboService(ctx, input.ID) svc := r.ProboService(ctx, input.ID)
var implemented *coredata.ControlImplementationState var maturityLevel *coredata.ControlMaturityLevel
if input.Implemented != nil { if input.MaturityLevel != nil {
v := coredata.ControlImplementationState(*input.Implemented) v := coredata.ControlMaturityLevel(*input.MaturityLevel)
implemented = &v maturityLevel = &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
} }
control, err := svc.Controls.Update( control, err := svc.Controls.Update(
@@ -1530,9 +1513,8 @@ func (r *Resolver) UpdateControlTool(ctx context.Context, req *mcp.CallToolReque
Description: UnwrapOmittable(input.Description), Description: UnwrapOmittable(input.Description),
SectionTitle: input.SectionTitle, SectionTitle: input.SectionTitle,
BestPractice: input.BestPractice, BestPractice: input.BestPractice,
Implemented: implemented,
NotImplementedJustification: UnwrapOmittable(input.NotImplementedJustification),
MaturityLevel: maturityLevel, MaturityLevel: maturityLevel,
NotImplementedJustification: UnwrapOmittable(input.NotImplementedJustification),
}, },
) )
if err != nil { if err != nil {

View File

@@ -4212,7 +4212,7 @@ components:
- section_title - section_title
- name - name
- best_practice - best_practice
- implemented - maturity_level
- created_at - created_at
- updated_at - updated_at
properties: properties:
@@ -4239,23 +4239,16 @@ components:
best_practice: best_practice:
type: boolean type: boolean
description: Whether control is a best practice description: Whether control is a best practice
implemented: maturity_level:
type: string type: string
enum: [IMPLEMENTED, NOT_IMPLEMENTED] enum: [NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING]
description: Control implementation state description: CMMI 0-5 maturity level of the control
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlImplementationState go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlMaturityLevel
not_implemented_justification: not_implemented_justification:
type: type:
- string - string
- "null" - "null"
description: Justification for non-implementation 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: created_at:
type: string type: string
format: date-time format: date-time
@@ -4330,7 +4323,7 @@ components:
- section_title - section_title
- name - name
- best_practice - best_practice
- implemented - maturity_level
properties: properties:
organization_id: organization_id:
$ref: "#/components/schemas/GID" $ref: "#/components/schemas/GID"
@@ -4350,23 +4343,16 @@ components:
best_practice: best_practice:
type: boolean type: boolean
description: Whether control is a best practice description: Whether control is a best practice
implemented: maturity_level:
type: string type: string
enum: [IMPLEMENTED, NOT_IMPLEMENTED] enum: [NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING]
description: Control implementation state description: CMMI 0-5 maturity level of the control
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlImplementationState go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlMaturityLevel
not_implemented_justification: not_implemented_justification:
type: type:
- string - string
- "null" - "null"
description: Justification for non-implementation 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: AddControlOutput:
type: object type: object
@@ -4397,21 +4383,15 @@ components:
best_practice: best_practice:
type: boolean type: boolean
description: Whether control is a best practice description: Whether control is a best practice
implemented: maturity_level:
type: string type: string
enum: [IMPLEMENTED, NOT_IMPLEMENTED] enum: [NONE, INITIAL, MANAGED, DEFINED, QUANTITATIVELY_MANAGED, OPTIMIZING]
description: Control implementation state description: CMMI 0-5 maturity level of the control
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlImplementationState go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ControlMaturityLevel
not_implemented_justification: not_implemented_justification:
type: ["string", "null"] type: ["string", "null"]
description: Justification for non-implementation description: Justification for non-implementation
go.probo.inc/mcpgen/omittable: true 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: UpdateControlOutput:
type: object type: object

View File

@@ -19,12 +19,6 @@ import (
) )
func NewControl(c *coredata.Control) *Control { func NewControl(c *coredata.Control) *Control {
var maturityLevel *string
if c.MaturityLevel != nil {
s := string(*c.MaturityLevel)
maturityLevel = &s
}
return &Control{ return &Control{
ID: c.ID, ID: c.ID,
OrganizationID: c.OrganizationID, OrganizationID: c.OrganizationID,
@@ -33,9 +27,8 @@ func NewControl(c *coredata.Control) *Control {
Name: c.Name, Name: c.Name,
Description: c.Description, Description: c.Description,
BestPractice: c.BestPractice, BestPractice: c.BestPractice,
Implemented: ControlImplemented(c.Implemented),
NotImplementedJustification: c.NotImplementedJustification, NotImplementedJustification: c.NotImplementedJustification,
MaturityLevel: maturityLevel, MaturityLevel: ControlMaturityLevel(c.MaturityLevel),
CreatedAt: c.CreatedAt, CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt, UpdatedAt: c.UpdatedAt,
} }