@@ -159,6 +159,115 @@ func (h *ReportHandler) GetFrameworkReportPDF(c *gin.Context) {
159159 writePDF (c , pdf , name )
160160}
161161
162+ // SetStatusOverride godoc
163+ //
164+ // @Summary Set a manual status override for a control
165+ // @Description Overrides the evaluator's computed status for a (framework, control) pair. Applied on live evaluations only; historical snapshots are unchanged.
166+ // @Tags Compliance Reports
167+ // @Security BearerAuth
168+ // @Accept json
169+ // @Produce json
170+ // @Param key path string true "Framework key"
171+ // @Param id path string true "Control id"
172+ // @Param body body object true "New status (COMPLIANT | NON_COMPLIANT | AT_RISK | NOT_COVERED | OUT_OF_SCOPE | PENDING) and optional reason"
173+ // @Success 204
174+ // @Failure 400 {object} map[string]string
175+ // @Failure 404 {object} map[string]string
176+ // @Router /compliance/frameworks/{key}/controls/{id}/status [put]
177+ func (h * ReportHandler ) SetStatusOverride (c * gin.Context ) {
178+ var body struct {
179+ Status string `json:"status"`
180+ Reason string `json:"reason"`
181+ }
182+ if err := c .ShouldBindJSON (& body ); err != nil {
183+ c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
184+ return
185+ }
186+ err := h .uc .SetStatusOverride (c .Request .Context (), c .Param ("key" ), c .Param ("id" ), body .Status , body .Reason )
187+ audit .Record (c , audit_connectors.Event {Action : "compliance.control.status.override" , ResourceType : "compliance_control" , ResourceID : c .Param ("id" )},
188+ audit_domain .COMPLIANCE_CONTROL_UPDATE_ATTEMPT , audit_domain .COMPLIANCE_CONTROL_UPDATE_SUCCESS , err )
189+ if err != nil {
190+ writeError (c , err )
191+ return
192+ }
193+ c .Status (http .StatusNoContent )
194+ }
195+
196+ // ClearStatusOverride godoc
197+ //
198+ // @Summary Clear a manual status override
199+ // @Description Removes the manual override so the control status falls back to the evaluator's computed value.
200+ // @Tags Compliance Reports
201+ // @Security BearerAuth
202+ // @Param key path string true "Framework key"
203+ // @Param id path string true "Control id"
204+ // @Success 204
205+ // @Failure 404 {object} map[string]string
206+ // @Router /compliance/frameworks/{key}/controls/{id}/status [delete]
207+ func (h * ReportHandler ) ClearStatusOverride (c * gin.Context ) {
208+ err := h .uc .ClearStatusOverride (c .Request .Context (), c .Param ("key" ), c .Param ("id" ))
209+ audit .Record (c , audit_connectors.Event {Action : "compliance.control.status.override.clear" , ResourceType : "compliance_control" , ResourceID : c .Param ("id" )},
210+ audit_domain .COMPLIANCE_CONTROL_UPDATE_ATTEMPT , audit_domain .COMPLIANCE_CONTROL_UPDATE_SUCCESS , err )
211+ if err != nil {
212+ writeError (c , err )
213+ return
214+ }
215+ c .Status (http .StatusNoContent )
216+ }
217+
218+ // SetControlNote godoc
219+ //
220+ // @Summary Set / update a user note on a control
221+ // @Description Upserts a freeform note attached to a (framework, control). Empty body deletes the note.
222+ // @Tags Compliance Reports
223+ // @Security BearerAuth
224+ // @Accept json
225+ // @Produce json
226+ // @Param key path string true "Framework key"
227+ // @Param id path string true "Control id"
228+ // @Param body body object true "Note body ({note: string})"
229+ // @Success 204
230+ // @Failure 400 {object} map[string]string
231+ // @Failure 404 {object} map[string]string
232+ // @Router /compliance/frameworks/{key}/controls/{id}/note [put]
233+ func (h * ReportHandler ) SetControlNote (c * gin.Context ) {
234+ var body struct {
235+ Note string `json:"note"`
236+ }
237+ if err := c .ShouldBindJSON (& body ); err != nil {
238+ c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
239+ return
240+ }
241+ err := h .uc .SetControlNote (c .Request .Context (), c .Param ("key" ), c .Param ("id" ), body .Note )
242+ audit .Record (c , audit_connectors.Event {Action : "compliance.control.note.set" , ResourceType : "compliance_control" , ResourceID : c .Param ("id" )},
243+ audit_domain .COMPLIANCE_CONTROL_UPDATE_ATTEMPT , audit_domain .COMPLIANCE_CONTROL_UPDATE_SUCCESS , err )
244+ if err != nil {
245+ writeError (c , err )
246+ return
247+ }
248+ c .Status (http .StatusNoContent )
249+ }
250+
251+ // ClearControlNote godoc
252+ //
253+ // @Summary Delete a user note on a control
254+ // @Tags Compliance Reports
255+ // @Security BearerAuth
256+ // @Param key path string true "Framework key"
257+ // @Param id path string true "Control id"
258+ // @Success 204
259+ // @Router /compliance/frameworks/{key}/controls/{id}/note [delete]
260+ func (h * ReportHandler ) ClearControlNote (c * gin.Context ) {
261+ err := h .uc .ClearControlNote (c .Request .Context (), c .Param ("key" ), c .Param ("id" ))
262+ audit .Record (c , audit_connectors.Event {Action : "compliance.control.note.clear" , ResourceType : "compliance_control" , ResourceID : c .Param ("id" )},
263+ audit_domain .COMPLIANCE_CONTROL_UPDATE_ATTEMPT , audit_domain .COMPLIANCE_CONTROL_UPDATE_SUCCESS , err )
264+ if err != nil {
265+ writeError (c , err )
266+ return
267+ }
268+ c .Status (http .StatusNoContent )
269+ }
270+
162271// @Summary Download a stored report snapshot as PDF
163272// @Tags Compliance Reports
164273// @Security BearerAuth
0 commit comments