Allow editing metadata of generated document versions

Title, document type, and classification on generated documents could
not be changed: any version-tracked field on a GENERATED write-mode
document was rejected with ErrDocumentVersionGenerated. The error now
fires only when content is being changed, so manual metadata edits
flow through the same draft-on-edit path as authored documents and
produce a draft version that the user can review and publish.

The CLI document update --document-type enum gains
STATEMENT_OF_APPLICABILITY (which generated SoA documents already
use), and the GraphQL resolver maps the content-edit rejection to a
Conflict instead of falling through to a generic Internal error.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-05-06 15:17:29 +02:00
parent 4e5958502c
commit 8bdab65963
7 changed files with 299 additions and 10 deletions

View File

@@ -106,7 +106,7 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
if err := cmdutil.ValidateEnum(
"document-type",
flagDocumentType,
[]string{"OTHER", "GOVERNANCE", "POLICY", "PROCEDURE", "PLAN", "REGISTER", "RECORD", "REPORT", "TEMPLATE"},
[]string{"OTHER", "GOVERNANCE", "POLICY", "PROCEDURE", "PLAN", "REGISTER", "RECORD", "REPORT", "TEMPLATE", "STATEMENT_OF_APPLICABILITY"},
); err != nil {
return err
}
@@ -174,7 +174,7 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command {
cmd.Flags().StringVar(&flagTitle, "title", "", "Document title")
cmd.Flags().StringVar(&flagContent, "content", "", "Document content")
cmd.Flags().StringVar(&flagDocumentType, "document-type", "", "Document type: OTHER, GOVERNANCE, POLICY, PROCEDURE, PLAN, REGISTER, RECORD, REPORT, TEMPLATE")
cmd.Flags().StringVar(&flagDocumentType, "document-type", "", "Document type: OTHER, GOVERNANCE, POLICY, PROCEDURE, PLAN, REGISTER, RECORD, REPORT, TEMPLATE, STATEMENT_OF_APPLICABILITY")
cmd.Flags().StringVar(&flagClassification, "classification", "", "Classification: PUBLIC, INTERNAL, CONFIDENTIAL, SECRET")
cmd.Flags().StringVar(&flagTrustCenterVisibility, "trust-center-visibility", "", "Trust center visibility: NONE, PRIVATE, PUBLIC")

View File

@@ -267,7 +267,7 @@ func (e ErrDocumentGenerated) Error() string {
}
func (e ErrDocumentVersionGenerated) Error() string {
return "cannot edit a generated document version"
return "cannot edit content of a generated document version"
}
func (e ErrDocumentVersionSignatureAlreadySigned) Error() string {
@@ -1800,7 +1800,7 @@ func (s *DocumentService) Update(
hasVersionChanges := req.Title != nil || req.Content != nil || req.Classification != nil || req.DocumentType != nil
if hasVersionChanges && document.WriteMode == coredata.DocumentWriteModeGenerated {
if req.Content != nil && document.WriteMode == coredata.DocumentWriteModeGenerated {
return &ErrDocumentVersionGenerated{}
}

View File

@@ -2909,6 +2909,19 @@ func (s *GeneratedDocumentService) publishOrRequestApproval(
minor bool,
now time.Time,
) error {
previousVersion := &coredata.DocumentVersion{}
err := previousVersion.LoadLatestVersion(ctx, tx, s.svc.scope, document.ID)
switch {
case err == nil:
version.Title = previousVersion.Title
version.Classification = previousVersion.Classification
version.DocumentType = previousVersion.DocumentType
case errors.Is(err, coredata.ErrResourceNotFound):
// First publish: keep the caller-provided defaults.
default:
return fmt.Errorf("cannot load previous document version: %w", err)
}
if minor {
if document.CurrentPublishedMajor == nil || document.CurrentPublishedMinor == nil {
return &ErrCannotPublishMinorWithoutMajor{}
@@ -2936,7 +2949,14 @@ func (s *GeneratedDocumentService) publishOrRequestApproval(
if err := version.Insert(ctx, tx, s.svc.scope); err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return fmt.Errorf("a version is pending approval, approve or reject it before publishing a new one: %w", err)
switch previousVersion.Status {
case coredata.DocumentVersionStatusDraft:
return fmt.Errorf("a draft version exists, publish or delete it before publishing a new one: %w", err)
case coredata.DocumentVersionStatusPendingApproval:
return fmt.Errorf("a version is pending approval, approve or reject it before publishing a new one: %w", err)
default:
return fmt.Errorf("a version already exists at this number: %w", err)
}
}
return fmt.Errorf("cannot insert document version: %w", err)
}

View File

@@ -864,6 +864,9 @@ func (r *mutationResolver) UpdateDocument(ctx context.Context, input types.Updat
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
return nil, gqlutils.Conflict(ctx, errArchived)
}
if errGenerated, ok := errors.AsType[*probo.ErrDocumentVersionGenerated](err); ok {
return nil, gqlutils.Conflict(ctx, errGenerated)
}
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
}