Draft-Commit Workflow Orchestration
Overview
The Engine orchestrates a draft-commit workflow for all package revision modifications. This pattern ensures atomicity - either all changes succeed and are persisted, or none are. The workflow uses mutable drafts for changes and immutable package revisions for storage.
High-Level Architecture
┌─────────────────────────────────────────────────────────┐
│ Draft-Commit Workflow Orchestration │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Draft Phase │ │ Commit Phase │ │
│ │ │ ───> │ │ │
│ │ • Open Draft │ │ • Close Draft │ │
│ │ • Apply Changes │ │ • Persist │ │
│ │ • Validate │ │ • Immutable │ │
│ └──────────────────┘ └──────────────────┘ │
│ │ │ │
│ └────────┬────────────────┘ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ Rollback │ │
│ │ Mechanism │ │
│ │ │ │
│ │• Trigger On Error│ │
│ │• Does Cleanup │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────┘
Draft-Commit Pattern
The Engine uses a two-phase workflow for all package revision modifications:
Pattern Overview
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Request │ ──────> │ Draft │ ──────> │ Immutable │
│ │ │ (Mutable) │ │ Package │
│ Create or │ │ │ │ Revision │
│ Update │ │ Changes │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│
│ Error?
↓
┌─────────────┐
│ Rollback │
│ Delete │
│ Draft │
└─────────────┘
Two phases:
- Draft Phase: Mutable workspace where changes are applied
- Commit Phase: Draft closed to create immutable package revision
Key characteristics:
- Atomicity: All changes succeed or all fail
- Isolation: Draft changes don’t affect other revisions
- Consistency: Validation before commit
- Durability: Committed revisions are immutable
Create Package Revision Workflow
The Engine orchestrates package revision creation through a draft-commit workflow:
Creation Flow
CreatePackageRevision Request
↓
Build Package Config
↓
Validate Request
↓
Open Repository
↓
Create Draft ──────────────┐
↓ │
Setup Rollback Handler │
↓ │
Apply Task │
↓ │
Error? ──Yes──> Rollback ──┘
│
No
↓
Update Lifecycle
↓
Error? ──Yes──> Rollback ──┘
│
No
↓
Close Draft
↓
Error? ──Yes──> Return Error (no rollback)
│
No
↓
Return PackageRevision
Process steps:
- Build package config from request and parent (if any)
- Validate request (lifecycle, tasks, workspace name, etc.)
- Open repository through cache
- Create draft in repository (mutable workspace)
- Setup rollback handler for error recovery
- Apply task through task handler (init, clone, edit, upgrade)
- Update lifecycle to requested state
- Close draft to create immutable package revision
- Return created package revision
Draft Creation
Repository.CreatePackageRevisionDraft
↓
Allocate Workspace
↓
Initialize Resources
↓
Return Draft Handle
Draft characteristics:
- Mutable: Can be modified before closing
- Isolated: Separate workspace from other revisions
- Temporary: Exists only during creation workflow
- Lightweight: No persistent storage until closed
Draft operations:
- UpdateResources: Modify package resources
- UpdateLifecycle: Change lifecycle state
- GetResources: Read current resources
- GetPackageRevision: Get package revision metadata
Task Application
TaskHandler.ApplyTask
↓
Execute Task Type
↓
┌────┴────┬────────┬─────────┐
↓ ↓ ↓ ↓
Init Clone Edit Upgrade
↓ ↓ ↓ ↓
└────┬────┴────────┴─────────┘
↓
Apply Builtin Functions
↓
Return Modified Draft
Task execution:
- Delegates to task handler for actual work
- Task handler modifies draft resources
- Builtin functions applied (package context, etc.)
- Draft returned with modifications
Error handling:
- Task errors trigger rollback
- Draft cleaned up on failure
- No partial package revisions created
Draft Closure
Repository.ClosePackageRevisionDraft
↓
Validate Draft State
↓
Persist to Storage
↓
Create Immutable PR
↓
Return PackageRevision
Closure process:
- Draft validated before closure
- Resources persisted to repository (Git commit/tag)
- Immutable package revision created
- Draft workspace cleaned up
Closure failure:
- Error returned to caller
- No rollback attempted (would likely fail again)
- Draft may remain in repository
- Manual cleanup may be required
Update Package Revision Workflow
The Engine orchestrates package revision updates through a draft-commit workflow:
Update Flow
UpdatePackageRevision Request
↓
Validate Resource Version
↓
Check Current Lifecycle
↓
Draft/Proposed? ──No──> Metadata Only Update
│
Yes
↓
Open Repository
↓
Update to Draft ────────────┐
↓ │
Apply Mutations │
↓ │
Error? ──Yes──> Return Error (draft cleanup?)
│
No
↓
Update Lifecycle
↓
Error? ──Yes──> Return Error
│
No
↓
Close Draft
↓
Error? ──Yes──> Return Error
│
No
↓
Update Metadata
↓
Notify Watchers
↓
Return Updated PR
Process steps:
- Validate resource version (optimistic locking)
- Check current lifecycle (determines update path)
- Open repository through cache
- Update to draft (opens existing revision as draft)
- Apply mutations through task handler
- Update lifecycle if changed
- Close draft to persist changes
- Update metadata (labels, annotations, finalizers)
- Notify watchers of change
- Return updated package revision
Draft Opening from Existing Revision
Repository.UpdatePackageRevision
↓
Load Existing Revision
↓
Create Draft Workspace
↓
Copy Resources to Draft
↓
Return Draft Handle
Draft from existing:
- Loads current package revision content
- Creates mutable draft workspace
- Copies resources to draft for modification
- Preserves original revision (immutable)
Draft operations:
- Same as creation draft
- Can modify resources, lifecycle, metadata
- Changes isolated until closed
Mutation Application
TaskHandler.DoPRMutations
↓
Compare Old vs New Spec
↓
Identify Changes
↓
New Tasks? ──Yes──> Apply New Tasks
│
No
↓
Return Modified Draft
Mutation types:
- Task additions: New tasks appended to task list
- Lifecycle changes: Handled separately (UpdateLifecycle)
- Metadata changes: Handled after draft closure
Mutation process:
- Compares old and new package revision specs
- Identifies new tasks to apply
- Delegates to task handler for execution
- Returns modified draft
Metadata-Only Update Path
UpdatePackageRevision (Published/DeletionProposed)
↓
Lifecycle Changed? ──Yes──> Update Lifecycle
│
No
↓
Update Metadata
↓
Notify Watchers
↓
Return Updated PR
Metadata-only updates:
- For Published and DeletionProposed package revisions
- No draft-commit workflow needed
- Direct metadata update on immutable revision
- Only labels, annotations, finalizers, owner references
Why metadata-only:
- Published packages are immutable (content cannot change)
- Metadata doesn’t affect package content
- Allows operational updates without content changes
Update Package Resources Workflow
The Engine orchestrates resource updates through a draft-commit workflow:
Resource Update Flow
UpdatePackageResources Request
↓
Get PackageRevision
↓
Validate Resource Version
↓
Check Lifecycle (Draft only)
↓
Open Repository
↓
Update to Draft
↓
Apply Resource Mutations
↓
Execute Render
↓
Error? ──Yes──> Return Error + RenderStatus
│
No
↓
Close Draft (no lifecycle change)
↓
Return PR + RenderStatus
Process steps:
- Get package revision from repository object
- Validate resource version (optimistic locking)
- Check lifecycle (must be Draft)
- Open repository through cache
- Update to draft (opens existing revision)
- Apply resource mutations through task handler
- Execute render (run function pipeline)
- Close draft without lifecycle change
- Return updated package revision and render status
Resource Mutation
TaskHandler.DoPRResourceMutations
↓
Update Package Resources
↓
Execute Render Task
↓
Run Function Pipeline
↓
Return RenderStatus
Resource mutation process:
- Updates package resource content directly
- Executes render task (runs KRM functions)
- Returns render status with function results
- No lifecycle change (resources updated in-place)
Render execution:
- Runs configured KRM function pipeline
- Functions can validate, transform, generate resources
- Results returned in RenderStatus
- Errors don’t prevent draft closure (render status indicates failure)
Rollback Mechanism
The Engine implements rollback to ensure atomicity on errors:
Rollback Strategy
Operation Error
↓
Rollback Handler Invoked
↓
Close Draft (version=0)
↓
Convert to PackageRevision
↓
Delete PackageRevision
↓
Success? ──No──> Log Warning
│
Yes
↓
Return Original Error
Rollback process:
- Error detected during operation
- Rollback handler invoked automatically
- Close draft with version=0 (converts to package revision)
- Delete package revision from repository
- Log warning if cleanup fails
- Return original error to caller
Rollback Handler Setup
Rollback handler creation:
When a draft is created, the engine sets up a rollback handler that will be invoked if any subsequent operation fails. The handler:
- Closes the draft with a special version indicator (0) to convert it to a package revision
- Deletes the package revision from the repository to clean up
- Logs warnings if cleanup fails (best-effort cleanup)
- Captures references to the draft and repository for later use
The rollback handler is a closure that captures the necessary context and can be invoked automatically when errors occur during the operation.
Rollback handler characteristics:
- Closure: Captures draft and repository references
- Best effort: Logs warning if cleanup fails
- Non-blocking: Doesn’t prevent error return
- Automatic: Invoked on any operation error
Rollback Triggers
When rollback invoked:
- Task application errors
- Lifecycle update errors
- Validation errors during operation
- Any error after draft creation
When rollback NOT invoked:
- Draft closure errors (would likely fail again)
- Errors before draft creation (nothing to clean up)
- Metadata update errors (draft already closed)
Rollback Limitations
Rollback may fail:
- Repository connection lost
- Permission errors
- Draft already closed
- Repository in inconsistent state
Failure handling:
- Warning logged with error details
- Original operation error still returned
- Draft may remain in repository
- Manual cleanup may be required
- Repository garbage collection may clean up eventually
Rollback vs Transaction
Not a true transaction:
- No two-phase commit
- No distributed transaction support
- Best-effort cleanup only
Why not a transaction:
- Git doesn’t support transactions
- Repository operations not transactional
- Rollback is cleanup, not undo
Atomicity guarantee:
- Either package revision created/updated or not
- No partial package revisions visible to clients
- Draft isolation prevents intermediate state visibility
Draft Lifecycle
Drafts have a specific lifecycle within the workflow:
Draft States
┌─────────────┐
│ Created │ ← CreatePackageRevisionDraft
└──────┬──────┘
│
↓
┌─────────────┐
│ Modified │ ← ApplyTask, UpdateLifecycle, UpdateResources
└──────┬──────┘
│
↓
┌─────────────┐
│ Closed │ ← ClosePackageRevisionDraft
└──────┬──────┘
│
↓
┌─────────────┐
│ Immutable │ ← PackageRevision created
│ Package │
│ Revision │
└─────────────┘
Draft lifecycle:
- Created: Draft allocated with empty or copied resources
- Modified: Changes applied through task handler
- Closed: Draft persisted to repository
- Immutable: Package revision created, draft cleaned up
Draft Isolation
Isolation guarantees:
- Draft changes not visible to other operations
- Draft workspace separate from other revisions
- Multiple drafts can exist simultaneously (different workspaces)
- Draft closure is atomic (all or nothing)
Concurrency:
- Multiple drafts for different packages: Fully concurrent
- Multiple drafts for same package: Prevented by workspace name uniqueness
- Draft and read operations: Concurrent (reads don’t see draft)
Workflow Optimization
The Engine optimizes the draft-commit workflow:
Optimization Strategies
Lazy draft creation:
- Draft created only when needed
- Not created for metadata-only updates
- Not created for read operations
Early validation:
- Validation before draft creation
- Fails fast without expensive operations
- Reduces rollback frequency
Efficient draft closure:
- Single repository operation
- Atomic commit to Git
- Minimal overhead
Performance Characteristics
Draft creation cost:
- Allocates workspace in repository
- Copies resources (for updates)
- Relatively lightweight
Draft modification cost:
- In-memory operations
- No repository access during modifications
- Task handler does actual work
Draft closure cost:
- Git commit/tag creation
- Repository write operation
- Most expensive part of workflow
Rollback cost:
- Draft closure + deletion
- Two repository operations
- Only on error path
Error Handling
The Engine handles errors at each workflow stage:
Error Categories
Pre-draft errors:
- Validation failures
- Repository access errors
- No rollback needed (no draft created)
Draft-phase errors:
- Task execution failures
- Lifecycle update failures
- Rollback invoked (draft cleanup)
Closure errors:
- Repository write failures
- Git operation failures
- No rollback (would likely fail again)
Post-closure errors:
- Metadata update failures
- Watcher notification failures
- Operation considered successful (package revision created)
Error Recovery
Client retry:
- Optimistic locking errors: Re-read and retry
- Transient errors: Retry with backoff
- Validation errors: Fix input and retry
Automatic recovery:
- Rollback on draft-phase errors
- Repository garbage collection for orphaned drafts
- Cache refresh for stale data
Manual recovery:
- Rollback failures may require manual cleanup
- Repository inspection and repair
- Draft deletion through repository tools