Add major.minor document versioning

Introduce a two-part version scheme (major.minor) for documents.
Drafts start at 0.1 and increment minor on each new draft. Publishing
as minor keeps the current version, publishing as major bumps to the
next major.0. Both current_published_major and current_published_minor
are tracked on the document for exact version lookups.

Signatures and approval quorums aggregate across all versions sharing
the same major number using CTE joins. Approval page mutations spread
the decision fragment so Relay updates the version row state without
requiring a page refresh.

GraphQL, MCP, and service layer expose separate publishMajor and
publishMinor mutations instead of a single mutation with a type enum.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-03-27 19:57:58 +01:00
parent c5a412ea41
commit 68bc8cf59a
31 changed files with 877 additions and 417 deletions

View File

@@ -2196,24 +2196,6 @@ func (r *Resolver) UpdateDocumentVersionTool(ctx context.Context, req *mcp.CallT
}, nil
}
func (r *Resolver) PublishDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.PublishDocumentVersionInput) (*mcp.CallToolResult, types.PublishDocumentVersionOutput, error) {
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish)
svc := r.ProboService(ctx, input.DocumentID)
user := authn.IdentityFromContext(ctx)
document, documentVersion, err := svc.Documents.PublishVersion(ctx, input.DocumentID, user.ID, input.Changelog)
if err != nil {
panic(fmt.Errorf("cannot publish document version: %w", err))
}
return nil, types.PublishDocumentVersionOutput{
Document: types.NewDocument(document),
DocumentVersion: types.NewDocumentVersion(documentVersion),
}, nil
}
func (r *Resolver) ListDocumentVersionSignaturesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListDocumentVersionSignaturesInput) (*mcp.CallToolResult, types.ListDocumentVersionSignaturesOutput, error) {
r.MustAuthorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionSignatureList)
@@ -3324,3 +3306,47 @@ func (r *Resolver) RequestDocumentVersionApprovalTool(ctx context.Context, req *
DocumentVersion: types.NewDocumentVersion(documentVersion),
}, nil
}
func (r *Resolver) PublishMajorDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.PublishMajorDocumentVersionInput) (*mcp.CallToolResult, types.PublishMajorDocumentVersionOutput, error) {
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish)
svc := r.ProboService(ctx, input.DocumentID)
user := authn.IdentityFromContext(ctx)
document, documentVersion, err := svc.Documents.PublishMajorVersion(
ctx,
input.DocumentID,
user.ID,
input.Changelog,
)
if err != nil {
panic(fmt.Errorf("cannot publish major document version: %w", err))
}
return nil, types.PublishMajorDocumentVersionOutput{
Document: types.NewDocument(document),
DocumentVersion: types.NewDocumentVersion(documentVersion),
}, nil
}
func (r *Resolver) PublishMinorDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.PublishMinorDocumentVersionInput) (*mcp.CallToolResult, types.PublishMinorDocumentVersionOutput, error) {
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish)
svc := r.ProboService(ctx, input.DocumentID)
user := authn.IdentityFromContext(ctx)
document, documentVersion, err := svc.Documents.PublishMinorVersion(
ctx,
input.DocumentID,
user.ID,
input.Changelog,
)
if err != nil {
panic(fmt.Errorf("cannot publish minor document version: %w", err))
}
return nil, types.PublishMinorDocumentVersionOutput{
Document: types.NewDocument(document),
DocumentVersion: types.NewDocumentVersion(documentVersion),
}, nil
}

View File

@@ -5207,11 +5207,16 @@ components:
classification:
$ref: "#/components/schemas/DocumentClassification"
description: Document classification
current_published_version:
current_published_major:
type:
- integer
- "null"
description: Current published version number
description: Current published major version number
current_published_minor:
type:
- integer
- "null"
description: Current published minor version number
trust_center_visibility:
$ref: "#/components/schemas/TrustCenterVisibility"
description: Trust center visibility
@@ -5240,7 +5245,8 @@ components:
- organization_id
- document_id
- title
- version_number
- major
- minor
- classification
- content
- changelog
@@ -5260,9 +5266,12 @@ components:
title:
type: string
description: Document version title
version_number:
major:
type: integer
description: Version number
description: Major version number
minor:
type: integer
description: Minor version number
classification:
$ref: "#/components/schemas/DocumentClassification"
description: Document classification
@@ -5606,7 +5615,19 @@ components:
$ref: "#/components/schemas/GID"
description: Deleted document version ID
PublishDocumentVersionInput:
PublishMajorDocumentVersionInput:
type: object
required:
- document_id
properties:
document_id:
$ref: "#/components/schemas/GID"
description: Document ID
changelog:
type: string
description: Changelog for this version
PublishMinorDocumentVersionInput:
type: object
required:
- document_id
@@ -7471,12 +7492,20 @@ tools:
$ref: "#/components/schemas/DeleteDraftDocumentVersionInput"
outputSchema:
$ref: "#/components/schemas/DeleteDraftDocumentVersionOutput"
- name: publishDocumentVersion
description: Publish a draft document version
- name: publishMajorDocumentVersion
description: Publish a draft document version as a new major version
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/PublishDocumentVersionInput"
$ref: "#/components/schemas/PublishMajorDocumentVersionInput"
outputSchema:
$ref: "#/components/schemas/PublishDocumentVersionOutput"
- name: publishMinorDocumentVersion
description: Publish a draft document version as a minor version
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/PublishMinorDocumentVersionInput"
outputSchema:
$ref: "#/components/schemas/PublishDocumentVersionOutput"
- name: requestDocumentVersionApproval

View File

@@ -21,17 +21,18 @@ import (
func NewDocument(d *coredata.Document) *Document {
return &Document{
ID: d.ID,
OrganizationID: d.OrganizationID,
Title: d.Title,
DocumentType: d.DocumentType,
Classification: d.Classification,
CurrentPublishedVersion: d.CurrentPublishedVersion,
TrustCenterVisibility: d.TrustCenterVisibility,
Status: d.Status,
ArchivedAt: d.ArchivedAt,
CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt,
ID: d.ID,
OrganizationID: d.OrganizationID,
Title: d.Title,
DocumentType: d.DocumentType,
Classification: d.Classification,
CurrentPublishedMajor: d.CurrentPublishedMajor,
CurrentPublishedMinor: d.CurrentPublishedMinor,
TrustCenterVisibility: d.TrustCenterVisibility,
Status: d.Status,
ArchivedAt: d.ArchivedAt,
CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt,
}
}
@@ -84,7 +85,8 @@ func NewDocumentVersion(dv *coredata.DocumentVersion) *DocumentVersion {
OrganizationID: dv.OrganizationID,
DocumentID: dv.DocumentID,
Title: dv.Title,
VersionNumber: dv.VersionNumber,
Major: dv.Major,
Minor: dv.Minor,
Classification: dv.Classification,
Content: dv.Content,
Changelog: dv.Changelog,