Add implemented state and justification to controls

Introduce `implemented` enum (IMPLEMENTED/NOT_IMPLEMENTED) and
`not_implemented_justification` (nullable text) fields on the Control
entity across all API surfaces (GraphQL, MCP, CLI), database, frontend,
and SOA export.

The database stores implementation state as a PostgreSQL enum
`control_implementation_state`. Controls default to IMPLEMENTED during
migration. The SOA list and PDF export show implementation status
alongside applicability, with "-" for non-applicable controls.
Justification columns are renamed for clarity: "Justification for
non-applicability" and "Justification for non-implementation".

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-03-16 19:24:34 +01:00
parent d8670d2412
commit cf1dadc0b5
25 changed files with 663 additions and 233 deletions

View File

@@ -64,6 +64,7 @@ func TestControl_Create(t *testing.T) {
"name": "Information Security Policies",
"description": "Policies for information security",
"bestPractice": true,
"implemented": "IMPLEMENTED",
},
}, &result)
require.NoError(t, err)
@@ -269,6 +270,7 @@ func TestControl_RequiredFields(t *testing.T) {
"description": "Test",
"sectionTitle": "Section 1",
"bestPractice": true,
"implemented": "IMPLEMENTED",
},
},
wantError: true,
@@ -281,6 +283,7 @@ func TestControl_RequiredFields(t *testing.T) {
"description": "Test",
"sectionTitle": "Section 1",
"bestPractice": true,
"implemented": "IMPLEMENTED",
},
},
wantError: true,
@@ -293,6 +296,7 @@ func TestControl_RequiredFields(t *testing.T) {
"name": "Test Control",
"description": "Test",
"bestPractice": true,
"implemented": "IMPLEMENTED",
},
},
wantError: true,
@@ -305,6 +309,7 @@ func TestControl_RequiredFields(t *testing.T) {
"name": "Test Control",
"sectionTitle": "Section 1",
"bestPractice": true,
"implemented": "IMPLEMENTED",
},
},
wantError: true,
@@ -317,6 +322,20 @@ func TestControl_RequiredFields(t *testing.T) {
"name": "Test Control",
"description": "Test",
"sectionTitle": "Section 1",
"implemented": "IMPLEMENTED",
},
},
wantError: true,
},
{
name: "Missing implemented should fail",
variables: map[string]any{
"input": map[string]any{
"frameworkId": frameworkID,
"name": "Test Control",
"description": "Test",
"sectionTitle": "Section 1",
"bestPractice": true,
},
},
wantError: true,
@@ -404,6 +423,7 @@ func TestControl_OmittableDescription(t *testing.T) {
"description": "Initial description",
"sectionTitle": "Section 1",
"bestPractice": true,
"implemented": "IMPLEMENTED",
},
}, &createResult)
require.NoError(t, err)
@@ -568,6 +588,7 @@ func TestControl_SubResolvers(t *testing.T) {
"description": "Test description",
"sectionTitle": "Section 1",
"bestPractice": true,
"implemented": "IMPLEMENTED",
},
}, &controlResult)
require.NoError(t, err)

View File

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

View File

@@ -384,7 +384,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}}
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"}}
},
shouldAllow: true,
},
@@ -394,7 +394,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}}
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"}}
},
shouldAllow: true,
},
@@ -404,7 +404,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}}
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"}}
},
shouldAllow: false,
},

View File

@@ -252,6 +252,11 @@ func CreateControl(c *testutil.Client, frameworkID string, attrs ...Attrs) strin
"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"),
}
if justification := a.getStringPtr("notImplementedJustification"); justification != nil {
input["notImplementedJustification"] = *justification
}
var result struct {
@@ -495,6 +500,16 @@ func (b *ControlBuilder) WithBestPractice(bestPractice bool) *ControlBuilder {
return b
}
func (b *ControlBuilder) WithImplemented(implemented string) *ControlBuilder {
b.attrs["implemented"] = implemented
return b
}
func (b *ControlBuilder) WithNotImplementedJustification(justification string) *ControlBuilder {
b.attrs["notImplementedJustification"] = justification
return b
}
func (b *ControlBuilder) Create() string {
return CreateControl(b.client, b.frameworkID, b.attrs)
}