Add finding and obligation publish to document system
Replace the old snapshot-based approach with the new publish document system for findings and obligations. Includes GraphQL mutations, MCP tools, CLI commands, e2e tests, frontend publish dialogs, and snapshot-to-document migration tools. Remove snapshot mode entirely from findings and obligations: drop snapshotId from GraphQL schemas, filters, resolvers, MCP spec, frontend routes, pages, and helpers. The snapshot_id column remains in the database but is now filtered out with snapshot_id IS NULL. Remove auditor's ability to publish SoA. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -181,10 +181,7 @@ func (r *auditResolver) Findings(ctx context.Context, obj *types.Audit, first *i
|
||||
ownerID = filter.OwnerID
|
||||
}
|
||||
|
||||
findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID)
|
||||
if filter != nil {
|
||||
findingFilter = coredata.NewFindingFilter(&filter.SnapshotID, kind, status, priority, ownerID)
|
||||
}
|
||||
findingFilter := coredata.NewFindingFilter(kind, status, priority, ownerID)
|
||||
|
||||
p, err := prb.Findings.ListForAuditID(ctx, obj.ID, cursor, findingFilter)
|
||||
if err != nil {
|
||||
@@ -363,10 +360,7 @@ func (r *findingConnectionResolver) TotalCount(ctx context.Context, obj *types.F
|
||||
ownerID = obj.Filter.OwnerID
|
||||
}
|
||||
|
||||
findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID)
|
||||
if obj.Filter != nil {
|
||||
findingFilter = coredata.NewFindingFilter(&obj.Filter.SnapshotID, kind, status, priority, ownerID)
|
||||
}
|
||||
findingFilter := coredata.NewFindingFilter(kind, status, priority, ownerID)
|
||||
|
||||
switch obj.Resolver.(type) {
|
||||
case *organizationResolver:
|
||||
@@ -677,6 +671,29 @@ func (r *mutationResolver) DeleteFindingAuditMapping(ctx context.Context, input
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PublishFindingList is the resolver for the publishFindingList field.
|
||||
func (r *mutationResolver) PublishFindingList(ctx context.Context, input types.PublishFindingListInput) (*types.PublishFindingListPayload, error) {
|
||||
if err := r.authorize(ctx, input.OrganizationID, probo.ActionFindingPublish); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
|
||||
|
||||
document, documentVersion, err := prb.GeneratedDocuments.PublishFindingList(ctx, input.OrganizationID, input.ApproverIds)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
|
||||
return nil, gqlutils.Conflict(ctx, err)
|
||||
}
|
||||
r.logger.ErrorCtx(ctx, "cannot publish finding list", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.PublishFindingListPayload{
|
||||
DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldCreatedAt),
|
||||
DocumentVersionEdge: types.NewDocumentVersionEdge(documentVersion, coredata.DocumentVersionOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DownloadURL is the resolver for the downloadUrl field.
|
||||
func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionReportDownloadUrlGet); err != nil {
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/vikstrous/dataloadgen"
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/dataloader"
|
||||
@@ -272,7 +271,7 @@ func (r *controlResolver) Audits(ctx context.Context, obj *types.Control, first
|
||||
}
|
||||
|
||||
// Obligations is the resolver for the obligations field.
|
||||
func (r *controlResolver) Obligations(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) {
|
||||
func (r *controlResolver) Obligations(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy) (*types.ObligationConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionObligationList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -292,18 +291,13 @@ func (r *controlResolver) Obligations(ctx context.Context, obj *types.Control, f
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
var snapshotID **gid.GID
|
||||
if filter != nil {
|
||||
snapshotID = &filter.SnapshotID
|
||||
}
|
||||
obligationFilter := coredata.NewObligationFilter(snapshotID)
|
||||
page, err := prb.Obligations.ListForControlID(ctx, obj.ID, cursor, obligationFilter)
|
||||
page, err := prb.Obligations.ListForControlID(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list control obligations", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewObligationConnection(page, r, obj.ID, filter), nil
|
||||
return types.NewObligationConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// Snapshots is the resolver for the snapshots field.
|
||||
|
||||
@@ -138,7 +138,6 @@ input FindingOrder {
|
||||
}
|
||||
|
||||
input FindingFilter {
|
||||
snapshotId: ID
|
||||
kind: FindingKind
|
||||
status: FindingStatus
|
||||
priority: FindingPriority
|
||||
@@ -171,7 +170,7 @@ type Audit implements Node {
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: FindingOrder
|
||||
filter: FindingFilter = { snapshotId: null }
|
||||
filter: FindingFilter
|
||||
): FindingConnection @goField(forceResolver: true)
|
||||
|
||||
trustCenterVisibility: TrustCenterVisibility!
|
||||
@@ -183,7 +182,6 @@ type Audit implements Node {
|
||||
|
||||
type Finding implements Node {
|
||||
id: ID!
|
||||
snapshotId: ID
|
||||
organization: Organization @goField(forceResolver: true)
|
||||
kind: FindingKind!
|
||||
referenceId: String!
|
||||
@@ -270,6 +268,19 @@ extend type Mutation {
|
||||
deleteFindingAuditMapping(
|
||||
input: DeleteFindingAuditMappingInput!
|
||||
): DeleteFindingAuditMappingPayload
|
||||
publishFindingList(
|
||||
input: PublishFindingListInput!
|
||||
): PublishFindingListPayload!
|
||||
}
|
||||
|
||||
input PublishFindingListInput {
|
||||
organizationId: ID!
|
||||
approverIds: [ID!]
|
||||
}
|
||||
|
||||
type PublishFindingListPayload {
|
||||
documentEdge: DocumentEdge!
|
||||
documentVersionEdge: DocumentVersionEdge!
|
||||
}
|
||||
|
||||
input CreateAuditInput {
|
||||
|
||||
@@ -139,7 +139,6 @@ type Control implements Node {
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: ObligationOrder
|
||||
filter: ObligationFilter
|
||||
): ObligationConnection! @goField(forceResolver: true)
|
||||
|
||||
snapshots(
|
||||
|
||||
@@ -51,14 +51,8 @@ input ObligationOrder
|
||||
field: ObligationOrderField!
|
||||
}
|
||||
|
||||
input ObligationFilter {
|
||||
snapshotId: ID
|
||||
}
|
||||
|
||||
type Obligation implements Node {
|
||||
id: ID!
|
||||
snapshotId: ID
|
||||
sourceId: ID
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
area: String
|
||||
source: String
|
||||
@@ -94,6 +88,19 @@ extend type Mutation {
|
||||
createObligation(input: CreateObligationInput!): CreateObligationPayload!
|
||||
updateObligation(input: UpdateObligationInput!): UpdateObligationPayload!
|
||||
deleteObligation(input: DeleteObligationInput!): DeleteObligationPayload!
|
||||
publishObligationList(
|
||||
input: PublishObligationListInput!
|
||||
): PublishObligationListPayload!
|
||||
}
|
||||
|
||||
input PublishObligationListInput {
|
||||
organizationId: ID!
|
||||
approverIds: [ID!]
|
||||
}
|
||||
|
||||
type PublishObligationListPayload {
|
||||
documentEdge: DocumentEdge!
|
||||
documentVersionEdge: DocumentVersionEdge!
|
||||
}
|
||||
|
||||
input CreateObligationInput {
|
||||
|
||||
@@ -150,13 +150,15 @@ type Organization implements Node {
|
||||
orderBy: AuditOrder
|
||||
): AuditConnection! @goField(forceResolver: true)
|
||||
|
||||
findingsDocument: Document @goField(forceResolver: true)
|
||||
|
||||
findings(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: FindingOrder
|
||||
filter: FindingFilter = { snapshotId: null }
|
||||
filter: FindingFilter
|
||||
): FindingConnection @goField(forceResolver: true)
|
||||
|
||||
auditLogEntries(
|
||||
@@ -247,13 +249,14 @@ type Organization implements Node {
|
||||
filter: MeasureFilter
|
||||
): MeasureConnection! @goField(forceResolver: true)
|
||||
|
||||
obligationsDocument: Document @goField(forceResolver: true)
|
||||
|
||||
obligations(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: ObligationOrder
|
||||
filter: ObligationFilter = { snapshotId: null }
|
||||
): ObligationConnection! @goField(forceResolver: true)
|
||||
|
||||
processingActivities(
|
||||
|
||||
@@ -107,7 +107,6 @@ type Risk implements Node {
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: ObligationOrder
|
||||
filter: ObligationFilter
|
||||
): ObligationConnection! @goField(forceResolver: true)
|
||||
|
||||
createdAt: Datetime!
|
||||
|
||||
@@ -3,14 +3,6 @@ enum SnapshotsType
|
||||
RISKS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeRisks")
|
||||
VENDORS
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeVendors")
|
||||
FINDINGS
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeFindings"
|
||||
)
|
||||
OBLIGATIONS
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeObligations"
|
||||
)
|
||||
PROCESSING_ACTIVITIES
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeProcessingActivities"
|
||||
|
||||
@@ -112,6 +112,29 @@ func (r *mutationResolver) DeleteObligation(ctx context.Context, input types.Del
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PublishObligationList is the resolver for the publishObligationList field.
|
||||
func (r *mutationResolver) PublishObligationList(ctx context.Context, input types.PublishObligationListInput) (*types.PublishObligationListPayload, error) {
|
||||
if err := r.authorize(ctx, input.OrganizationID, probo.ActionObligationPublish); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
|
||||
|
||||
document, documentVersion, err := prb.GeneratedDocuments.PublishObligationList(ctx, input.OrganizationID, input.ApproverIds)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
|
||||
return nil, gqlutils.Conflict(ctx, err)
|
||||
}
|
||||
r.logger.ErrorCtx(ctx, "cannot publish obligation list", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.PublishObligationListPayload{
|
||||
DocumentEdge: types.NewDocumentEdge(document, coredata.DocumentOrderFieldCreatedAt),
|
||||
DocumentVersionEdge: types.NewDocumentVersionEdge(documentVersion, coredata.DocumentVersionOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *obligationResolver) Organization(ctx context.Context, obj *types.Obligation) (*types.Organization, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil {
|
||||
@@ -169,24 +192,14 @@ func (r *obligationConnectionResolver) TotalCount(ctx context.Context, obj *type
|
||||
|
||||
switch obj.Resolver.(type) {
|
||||
case *organizationResolver:
|
||||
obligationFilter := coredata.NewObligationFilter(nil)
|
||||
if obj.Filter != nil {
|
||||
obligationFilter = coredata.NewObligationFilter(&obj.Filter.SnapshotID)
|
||||
}
|
||||
|
||||
count, err := prb.Obligations.CountForOrganizationID(ctx, obj.ParentID, obligationFilter)
|
||||
count, err := prb.Obligations.CountForOrganizationID(ctx, obj.ParentID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot count obligations", log.Error(err))
|
||||
return 0, gqlutils.Internal(ctx)
|
||||
}
|
||||
return count, nil
|
||||
case *riskResolver:
|
||||
obligationFilter := coredata.NewObligationFilter(nil)
|
||||
if obj.Filter != nil {
|
||||
obligationFilter = coredata.NewObligationFilter(&obj.Filter.SnapshotID)
|
||||
}
|
||||
|
||||
count, err := prb.Obligations.CountForRiskID(ctx, obj.ParentID, obligationFilter)
|
||||
count, err := prb.Obligations.CountForRiskID(ctx, obj.ParentID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot count risk obligations", log.Error(err))
|
||||
return 0, gqlutils.Internal(ctx)
|
||||
|
||||
@@ -362,6 +362,30 @@ func (r *organizationResolver) Audits(ctx context.Context, obj *types.Organizati
|
||||
return types.NewAuditConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// FindingsDocument is the resolver for the findingsDocument field.
|
||||
func (r *organizationResolver) FindingsDocument(ctx context.Context, obj *types.Organization) (*types.Document, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionDocumentGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
findingDocumentID, err := prb.GeneratedDocuments.GetFindingsDocumentID(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get finding list document ID: %w", err)
|
||||
}
|
||||
if findingDocumentID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
doc, err := prb.Documents.Get(ctx, *findingDocumentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get finding list document: %w", err)
|
||||
}
|
||||
|
||||
return types.NewDocument(doc), nil
|
||||
}
|
||||
|
||||
// Findings is the resolver for the findings field.
|
||||
func (r *organizationResolver) Findings(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FindingOrder, filter *types.FindingFilter) (*types.FindingConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionFindingList); err != nil {
|
||||
@@ -397,10 +421,7 @@ func (r *organizationResolver) Findings(ctx context.Context, obj *types.Organiza
|
||||
ownerID = filter.OwnerID
|
||||
}
|
||||
|
||||
findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID)
|
||||
if filter != nil {
|
||||
findingFilter = coredata.NewFindingFilter(&filter.SnapshotID, kind, status, priority, ownerID)
|
||||
}
|
||||
findingFilter := coredata.NewFindingFilter(kind, status, priority, ownerID)
|
||||
|
||||
page, err := prb.Findings.ListForOrganizationID(ctx, obj.ID, cursor, findingFilter)
|
||||
if err != nil {
|
||||
@@ -791,8 +812,32 @@ func (r *organizationResolver) Measures(ctx context.Context, obj *types.Organiza
|
||||
return types.NewMeasureConnection(page, r, obj.ID, measureFilter), nil
|
||||
}
|
||||
|
||||
// ObligationsDocument is the resolver for the obligationsDocument field.
|
||||
func (r *organizationResolver) ObligationsDocument(ctx context.Context, obj *types.Organization) (*types.Document, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionDocumentGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
obligationDocumentID, err := prb.GeneratedDocuments.GetObligationsDocumentID(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get obligation list document ID: %w", err)
|
||||
}
|
||||
if obligationDocumentID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
doc, err := prb.Documents.Get(ctx, *obligationDocumentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get obligation list document: %w", err)
|
||||
}
|
||||
|
||||
return types.NewDocument(doc), nil
|
||||
}
|
||||
|
||||
// Obligations is the resolver for the obligations field.
|
||||
func (r *organizationResolver) Obligations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) {
|
||||
func (r *organizationResolver) Obligations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy) (*types.ObligationConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionObligationList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -813,18 +858,13 @@ func (r *organizationResolver) Obligations(ctx context.Context, obj *types.Organ
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
obligationFilter := coredata.NewObligationFilter(nil)
|
||||
if filter != nil {
|
||||
obligationFilter = coredata.NewObligationFilter(&filter.SnapshotID)
|
||||
}
|
||||
|
||||
page, err := prb.Obligations.ListForOrganizationID(ctx, obj.ID, cursor, obligationFilter)
|
||||
page, err := prb.Obligations.ListForOrganizationID(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list organization obligations", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewObligationConnection(page, r, obj.ID, filter), nil
|
||||
return types.NewObligationConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// ProcessingActivities is the resolver for the processingActivities field.
|
||||
|
||||
@@ -393,7 +393,7 @@ func (r *riskResolver) Controls(ctx context.Context, obj *types.Risk, first *int
|
||||
}
|
||||
|
||||
// Obligations is the resolver for the obligations field.
|
||||
func (r *riskResolver) Obligations(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) {
|
||||
func (r *riskResolver) Obligations(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy) (*types.ObligationConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionObligationList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -413,18 +413,13 @@ func (r *riskResolver) Obligations(ctx context.Context, obj *types.Risk, first *
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
var obligationFilter = coredata.NewObligationFilter(nil)
|
||||
if filter != nil {
|
||||
obligationFilter = coredata.NewObligationFilter(&filter.SnapshotID)
|
||||
}
|
||||
|
||||
page, err := prb.Obligations.ListForRiskID(ctx, obj.ID, cursor, obligationFilter)
|
||||
page, err := prb.Obligations.ListForRiskID(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list risk obligations", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewObligationConnection(page, r, obj.ID, filter), nil
|
||||
return types.NewObligationConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
|
||||
@@ -67,8 +67,7 @@ func NewFindingEdge(f *coredata.Finding, orderField coredata.FindingOrderField)
|
||||
|
||||
func NewFinding(f *coredata.Finding) *Finding {
|
||||
finding := &Finding{
|
||||
ID: f.ID,
|
||||
SnapshotID: f.SnapshotID,
|
||||
ID: f.ID,
|
||||
Organization: &Organization{
|
||||
ID: f.OrganizationID,
|
||||
},
|
||||
|
||||
@@ -30,7 +30,6 @@ type (
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
Filter *ObligationFilter
|
||||
}
|
||||
)
|
||||
|
||||
@@ -38,7 +37,6 @@ func NewObligationConnection(
|
||||
p *page.Page[*coredata.Obligation, coredata.ObligationOrderField],
|
||||
parentType any,
|
||||
parentID gid.GID,
|
||||
filter *ObligationFilter,
|
||||
) *ObligationConnection {
|
||||
edges := make([]*ObligationEdge, len(p.Data))
|
||||
for i, obligation := range p.Data {
|
||||
@@ -51,15 +49,12 @@ func NewObligationConnection(
|
||||
|
||||
Resolver: parentType,
|
||||
ParentID: parentID,
|
||||
Filter: filter,
|
||||
}
|
||||
}
|
||||
|
||||
func NewObligation(cr *coredata.Obligation) *Obligation {
|
||||
return &Obligation{
|
||||
ID: cr.ID,
|
||||
SnapshotID: cr.SnapshotID,
|
||||
SourceID: cr.SourceID,
|
||||
ID: cr.ID,
|
||||
Organization: &Organization{
|
||||
ID: cr.OrganizationID,
|
||||
},
|
||||
|
||||
@@ -741,11 +741,9 @@ func (r *Resolver) ListFindingsTool(ctx context.Context, req *mcp.CallToolReques
|
||||
|
||||
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
|
||||
|
||||
noSnapshot := (*gid.GID)(nil)
|
||||
findingFilter := coredata.NewFindingFilter(&noSnapshot, nil, nil, nil, nil)
|
||||
findingFilter := coredata.NewFindingFilter(nil, nil, nil, nil)
|
||||
if input.Filter != nil {
|
||||
findingFilter = coredata.NewFindingFilter(
|
||||
&input.Filter.SnapshotID,
|
||||
input.Filter.Kind,
|
||||
input.Filter.Status,
|
||||
input.Filter.Priority,
|
||||
@@ -857,13 +855,7 @@ func (r *Resolver) ListObligationsTool(ctx context.Context, req *mcp.CallToolReq
|
||||
|
||||
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
|
||||
|
||||
noSnapshot := (*gid.GID)(nil)
|
||||
obligationFilter := coredata.NewObligationFilter(&noSnapshot)
|
||||
if input.Filter != nil {
|
||||
obligationFilter = coredata.NewObligationFilter(&input.Filter.SnapshotID)
|
||||
}
|
||||
|
||||
page, err := prb.Obligations.ListForOrganizationID(ctx, input.OrganizationID, cursor, obligationFilter)
|
||||
page, err := prb.Obligations.ListForOrganizationID(ctx, input.OrganizationID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list organization obligations: %w", err))
|
||||
}
|
||||
@@ -1610,7 +1602,7 @@ func (r *Resolver) ListControlObligationsTool(ctx context.Context, req *mcp.Call
|
||||
|
||||
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
|
||||
|
||||
obligationPage, err := prb.Obligations.ListForControlID(ctx, input.ControlID, cursor, coredata.NewObligationFilter(nil))
|
||||
obligationPage, err := prb.Obligations.ListForControlID(ctx, input.ControlID, cursor)
|
||||
if err != nil {
|
||||
return nil, types.ListControlObligationsOutput{}, fmt.Errorf("failed to list control obligations: %w", err)
|
||||
}
|
||||
@@ -1740,7 +1732,7 @@ func (r *Resolver) ListRiskObligationsTool(ctx context.Context, req *mcp.CallToo
|
||||
|
||||
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
|
||||
|
||||
obligationPage, err := prb.Obligations.ListForRiskID(ctx, input.RiskID, cursor, coredata.NewObligationFilter(nil))
|
||||
obligationPage, err := prb.Obligations.ListForRiskID(ctx, input.RiskID, cursor)
|
||||
if err != nil {
|
||||
return nil, types.ListRiskObligationsOutput{}, fmt.Errorf("failed to list risk obligations: %w", err)
|
||||
}
|
||||
@@ -4786,3 +4778,35 @@ func (r *Resolver) AssessVendorTool(ctx context.Context, req *mcp.CallToolReques
|
||||
|
||||
return nil, types.NewAssessVendorOutput(result), nil
|
||||
}
|
||||
|
||||
func (r *Resolver) PublishFindingListTool(ctx context.Context, req *mcp.CallToolRequest, input *types.PublishFindingListInput) (*mcp.CallToolResult, types.PublishFindingListOutput, error) {
|
||||
r.MustAuthorize(ctx, input.OrganizationID, probo.ActionFindingPublish)
|
||||
|
||||
svc := r.ProboService(ctx, input.OrganizationID)
|
||||
|
||||
document, documentVersion, err := svc.GeneratedDocuments.PublishFindingList(ctx, input.OrganizationID, input.ApproverIds)
|
||||
if err != nil {
|
||||
return nil, types.PublishFindingListOutput{}, fmt.Errorf("cannot publish finding list: %w", err)
|
||||
}
|
||||
|
||||
return nil, types.PublishFindingListOutput{
|
||||
DocumentID: document.ID,
|
||||
DocumentVersionID: documentVersion.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Resolver) PublishObligationListTool(ctx context.Context, req *mcp.CallToolRequest, input *types.PublishObligationListInput) (*mcp.CallToolResult, types.PublishObligationListOutput, error) {
|
||||
r.MustAuthorize(ctx, input.OrganizationID, probo.ActionObligationPublish)
|
||||
|
||||
svc := r.ProboService(ctx, input.OrganizationID)
|
||||
|
||||
document, documentVersion, err := svc.GeneratedDocuments.PublishObligationList(ctx, input.OrganizationID, input.ApproverIds)
|
||||
if err != nil {
|
||||
return nil, types.PublishObligationListOutput{}, fmt.Errorf("cannot publish obligation list: %w", err)
|
||||
}
|
||||
|
||||
return nil, types.PublishObligationListOutput{
|
||||
DocumentID: document.ID,
|
||||
DocumentVersionID: documentVersion.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -2827,18 +2827,6 @@ components:
|
||||
organization_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Organization ID
|
||||
snapshot_id:
|
||||
anyOf:
|
||||
- $ref: "#/components/schemas/GID"
|
||||
description: Snapshot ID
|
||||
- type: "null"
|
||||
description: No snapshot
|
||||
description: Snapshot ID
|
||||
source_id:
|
||||
type:
|
||||
- string
|
||||
- "null"
|
||||
description: Source ID
|
||||
kind:
|
||||
$ref: "#/components/schemas/FindingKind"
|
||||
description: Finding kind
|
||||
@@ -2929,12 +2917,6 @@ components:
|
||||
filter:
|
||||
type: object
|
||||
properties:
|
||||
snapshot_id:
|
||||
anyOf:
|
||||
- $ref: "#/components/schemas/GID"
|
||||
- type: "null"
|
||||
description: Filter by snapshot ID. Defaults to null, which returns only findings with no snapshot (current live data). Pass a specific snapshot ID to retrieve findings as they were at that snapshot.
|
||||
default: null
|
||||
kind:
|
||||
anyOf:
|
||||
- $ref: "#/components/schemas/FindingKind"
|
||||
@@ -3279,18 +3261,6 @@ components:
|
||||
organization_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Organization ID
|
||||
snapshot_id:
|
||||
anyOf:
|
||||
- $ref: "#/components/schemas/GID"
|
||||
description: Snapshot ID
|
||||
- type: "null"
|
||||
description: No snapshot
|
||||
description: Snapshot ID
|
||||
source_id:
|
||||
type:
|
||||
- string
|
||||
- "null"
|
||||
description: Source ID
|
||||
area:
|
||||
type:
|
||||
- string
|
||||
@@ -3363,16 +3333,6 @@ components:
|
||||
cursor:
|
||||
$ref: "#/components/schemas/CursorKey"
|
||||
description: Page cursor
|
||||
filter:
|
||||
type: object
|
||||
properties:
|
||||
snapshot_id:
|
||||
anyOf:
|
||||
- $ref: "#/components/schemas/GID"
|
||||
- type: "null"
|
||||
description: Filter by snapshot ID. Defaults to null, which returns only obligations with no snapshot (current live data). Pass a specific snapshot ID to retrieve obligations as they were at that snapshot.
|
||||
default: null
|
||||
|
||||
ListObligationsOutput:
|
||||
type: object
|
||||
required:
|
||||
@@ -7051,6 +7011,60 @@ components:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Created document version ID
|
||||
|
||||
PublishFindingListInput:
|
||||
type: object
|
||||
required:
|
||||
- organization_id
|
||||
properties:
|
||||
organization_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Organization ID
|
||||
approver_ids:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
|
||||
|
||||
PublishFindingListOutput:
|
||||
type: object
|
||||
required:
|
||||
- document_id
|
||||
- document_version_id
|
||||
properties:
|
||||
document_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Created or updated document ID
|
||||
document_version_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Created document version ID
|
||||
|
||||
PublishObligationListInput:
|
||||
type: object
|
||||
required:
|
||||
- organization_id
|
||||
properties:
|
||||
organization_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Organization ID
|
||||
approver_ids:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
|
||||
|
||||
PublishObligationListOutput:
|
||||
type: object
|
||||
required:
|
||||
- document_id
|
||||
- document_version_id
|
||||
properties:
|
||||
document_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Created or updated document ID
|
||||
document_version_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Created document version ID
|
||||
|
||||
PublishStatementOfApplicabilityInput:
|
||||
type: object
|
||||
required:
|
||||
@@ -10288,6 +10302,22 @@ tools:
|
||||
$ref: "#/components/schemas/PublishAssetListInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/PublishAssetListOutput"
|
||||
- name: publishFindingList
|
||||
description: Publish the finding register for an organization as a document. If a document already exists, a new version is created.
|
||||
hints:
|
||||
readonly: false
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/PublishFindingListInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/PublishFindingListOutput"
|
||||
- name: publishObligationList
|
||||
description: Publish the obligation register for an organization as a document. If a document already exists, a new version is created.
|
||||
hints:
|
||||
readonly: false
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/PublishObligationListInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/PublishObligationListOutput"
|
||||
- name: publishStatementOfApplicability
|
||||
description: Publish a statement of applicability as a document. If a document already exists, a new version is created.
|
||||
hints:
|
||||
|
||||
@@ -23,7 +23,6 @@ func NewFinding(f *coredata.Finding) *Finding {
|
||||
finding := &Finding{
|
||||
ID: f.ID,
|
||||
OrganizationID: f.OrganizationID,
|
||||
SnapshotID: f.SnapshotID,
|
||||
Kind: f.Kind,
|
||||
ReferenceID: f.ReferenceID,
|
||||
Description: f.Description,
|
||||
@@ -41,11 +40,6 @@ func NewFinding(f *coredata.Finding) *Finding {
|
||||
UpdatedAt: f.UpdatedAt,
|
||||
}
|
||||
|
||||
if f.SourceID != nil {
|
||||
s := f.SourceID.String()
|
||||
finding.SourceID = &s
|
||||
}
|
||||
|
||||
return finding
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ func NewObligation(o *coredata.Obligation) *Obligation {
|
||||
obligation := &Obligation{
|
||||
ID: o.ID,
|
||||
OrganizationID: o.OrganizationID,
|
||||
SnapshotID: o.SnapshotID,
|
||||
Area: o.Area,
|
||||
Source: o.Source,
|
||||
Requirement: o.Requirement,
|
||||
@@ -38,11 +37,6 @@ func NewObligation(o *coredata.Obligation) *Obligation {
|
||||
UpdatedAt: o.UpdatedAt,
|
||||
}
|
||||
|
||||
if o.SourceID != nil {
|
||||
s := o.SourceID.String()
|
||||
obligation.SourceID = &s
|
||||
}
|
||||
|
||||
return obligation
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user