Policy Template Versioning Architecture¶
Overview¶
GetCimple's policy versioning system enables us to continuously improve policy templates while customers maintain their configurations and implementation progress. The system uses a configuration-over-customization model that eliminates merge conflicts and provides automatic updates.
Key Innovation: Locked best-practice content + configurable parameters = Zero-conflict updates
Architectural Decision: Configuration vs Customization¶
The Core Choice¶
We had to decide: Do we let customers fully customize policy content, or do we limit them to configuration?
Decision: Configuration-based policies with locked content
Rationale¶
Option A: Full Customization (Rejected)¶
Model: Customers can edit any part of the policy
β Problems:
- Merge conflicts when we release updates
- Three-way diff required (our v1.0 β their customized v1.0 β our v1.1)
- Complex UI for resolving conflicts
- Can't guarantee compliance if content is modified
- High support burden
- Customers may introduce errors
Option B: Configuration-Based (Selected)¶
Model: Core content is locked, customers configure parameters
β Benefits:
- Zero merge conflicts (reapply configuration = auto-merge)
- Simple UI (just configure new parameters)
- Compliance guarantee (we control content)
- Fast updates (minutes not hours)
- Customers trust expert-maintained content
- Aligns with industry practice (Vanta, Drata, Secureframe)
Real-World Validation¶
How policies are created today:
- Hire consultant ($5k-20k) β Use their template, fill in specifics
- Download ISO/NIST template β Configure for organization
- Write from scratch β Almost nobody does this
Insight: Companies already use locked templates from experts. We're providing the same model as a service.
System Architecture¶
High-Level Flow¶
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GetCimple Maintains β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Policy Templates β β
β β βββ v1.0 (Released Jan 2025) β β
β β βββ v1.1 (Released Jun 2025) β New version β β
β β βββ v2.0 (Released Dec 2025) β β
β β β β
β β Content: β β
β β β’ Locked sections (best practice text) β β
β β β’ Variable definitions β β
β β β’ Parameter definitions (dropdowns, ranges) β β
β β β’ Optional section definitions β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
Update Detection
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Customer Policies β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Active: Access Control Policy v1.0 β β
β β Configuration: β β
β β variables: {company_name: "Acme", ciso: "Jane"} β β
β β parameters: {password_length: 14, frequency: "Q"} β β
β β sections: ["cloud", "contractors"] β β
β β addenda: "Our implementation timeline..." β β
β β Progress: 40% complete β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β Notification: "Update available" β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Draft: Access Control Policy v1.1 β β
β β Auto-merged configuration: β β
β β β Variables preserved β β
β β β Parameters preserved (where still valid) β β
β β β Sections preserved (where still exist) β β
β β β Addenda preserved β β
β β β οΈ New parameters: Need review β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β Customer reviews & approves β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Active: Access Control Policy v1.1 β β β
β β Archived: v1.0 (Jan-Jun 2025) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Component Architecture¶
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API Layer β
β β
β GET /api/policies/updates β Check updates β
β POST /api/policies/{id}/draft β Create draft β
β PATCH /api/policies/{id}/draft β Update config β
β POST /api/policies/{id}/draft/approve β Activate β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Business Logic Layer β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Update Detection Service ββ
β β β’ Query: customer version < latest version? ββ
β β β’ Returns: List of available updates ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Auto-Merge Service ββ
β β β’ Takes: Current config + New template ββ
β β β’ Returns: Merged config with review items ββ
β β β’ Logic: Reapply values where keys match ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Progress Mapping Service ββ
β β β’ Takes: Old progress + New template sections ββ
β β β’ Returns: Mapped progress (by section ID) ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Validation Service ββ
β β β’ Validates config against template definitions ββ
β β β’ Checks parameter ranges, required fields ββ
β β β’ Post-MVP: Dependency validation ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Data Layer β
β β
β ββββββββββββββββββββββββ βββββββββββββββββββββββββββ β
β β policy_templates β β customer_policy_ β β
β β β β instances β β
β β β’ current_version β β β β
β β β’ locked_content β β β’ active_version β β
β β β’ variable_defs β β β’ configuration β β
β β β’ parameter_defs β β β’ draft_version β β
β β β’ section_defs β β β’ draft_configuration β β
β ββββββββββββββββββββββββ βββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β policy_version_archives ββ
β β β’ Audit trail of previous versions ββ
β β β’ active_from / active_until dates ββ
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Configuration Model Deep Dive¶
Policy Structure¶
Every policy template consists of four configuration layers:
1. Locked Content [NOT EDITABLE]¶
## Password Requirements
All user accounts at {{company_name}} must use passwords of at
least {{password_length}} characters. Passwords must be changed
every {{password_expiry_days}} days.
This requirement aligns with Essential Eight Maturity Level 2
for authentication hardening.
Characteristics:
- Maintained by GetCimple compliance experts
- Updated to reflect regulatory changes
- Markdown with
{{variable}}placeholders - Maps to compliance frameworks (E8, ISO 27001)
2. Variables [TEXT SUBSTITUTION]¶
Characteristics:
- Simple key-value pairs
- Always preserved during updates
- Used for organization-specific names/tools
3. Parameters [BOUNDED CONFIGURATION]¶
{
"password_length": 14, // Range: 12-16
"password_expiry_days": 90, // Range: 60-180
"review_frequency": "quarterly" // Options: monthly/quarterly/annually
}
Characteristics:
- Bounded selections within best practices
- Dropdowns, number ranges, dates
- Smart migration: preserve if still valid, use default if not
4. Optional Sections [TOGGLE ON/OFF]¶
Characteristics:
- Enable/disable based on applicability
- Examples: Cloud services, Contractors, Remote work
- Only kept if still exist in new version
5. Company-Specific Addenda [FREEFORM]¶
## Our Implementation Timeline
Phase 1 (Q1 2025): All administrative accounts
Phase 2 (Q2 2025): All user accounts
## Approved Exceptions
Legacy system X: To be migrated by Q3 2025
Characteristics:
- Markdown freeform text
- Rendered as formal appendix to policy
- Always preserved during updates
- Escape hatch for company context
Auto-Merge Algorithm¶
How Updates Work Without Conflicts¶
function autoMerge(
oldConfig: Configuration,
newTemplate: Template
): MergedConfiguration {
return {
// 1. Variables: Always carry forward (simple)
variables: oldConfig.variables,
// 2. Parameters: Smart migration
parameters: mergeParameters(
oldConfig.parameters,
newTemplate.parameter_definitions
),
// 3. Sections: Filter to still-valid
enabled_sections: oldConfig.enabled_sections.filter(
(sectionId) => newTemplate.section_definitions[sectionId] !== undefined
),
// 4. Addenda: Always preserve
company_addenda: oldConfig.company_addenda,
}
}
function mergeParameters(old, newDefs) {
const merged = {}
for (const [key, definition] of Object.entries(newDefs)) {
if (old[key] !== undefined && isValidValue(old[key], definition)) {
// Parameter exists and still valid β keep it
merged[key] = old[key]
} else {
// Parameter new or invalid β use default
merged[key] = definition.default
}
}
return merged
}
Why This Works¶
No conflicts possible because:
- Locked content can't conflict (customer never edited it)
- Variables are simple key-value (always preserve)
- Parameters are bounded (validate and default if needed)
- Sections are toggles (filter to valid IDs)
- Addenda is freeform (always append)
Result: 100% automatic merge success rate
Update Workflow States¶
State Machine¶
βββββββββββββββ
β Active β Customer using v1.0
β v1.0 β Board-approved, implementing
ββββββββ¬βββββββ
β
β We release v1.1
β
βββββββββββββββ
β Notified β Dashboard: "Update available"
β β Customer can review anytime
ββββββββ¬βββββββ
β
β Customer clicks "Review Update"
β
βββββββββββββββ
β Draft β Auto-merged draft created
β Created β v1.0 still active, v1.1 in draft
ββββββββ¬βββββββ
β
β Customer configures new parameters
β
βββββββββββββββ
β Reviewing β Customer reviews changes
β β Can edit draft configuration
ββββββββ¬βββββββ
β
β Customer takes to board
β
βββββββββββββββ
β Approved β Board approves v1.1
β β Ready to activate
ββββββββ¬βββββββ
β
β System activates draft
β
βββββββββββββββ βββββββββββββββ
β Active β β Archived β
β v1.1 β β v1.0 β
β β β (Jan-Jun 25)β
βββββββββββββββ βββββββββββββββ
Database States¶
-- State 1: Active v1.0, no draft
active_template_version = '1.0'
draft_template_version = NULL
status = 'implementing'
-- State 2: Active v1.0, draft v1.1 in progress
active_template_version = '1.0'
draft_template_version = '1.1'
draft_configuration = {...}
status = 'implementing'
-- State 3: v1.1 activated, v1.0 archived
active_template_version = '1.1'
draft_template_version = NULL
status = 'approved'
-- Archive entry created:
INSERT INTO policy_version_archives (
template_version = '1.0',
active_from = '2025-01-15',
active_until = '2025-06-15'
)
Implementation Progress Mapping¶
Challenge¶
When updating from v1.0 to v1.1:
- v1.0 had 8 sections, 40% complete overall
- v1.1 has 10 sections (2 new, 8 unchanged)
- How do we map progress?
Solution: Section ID Matching¶
interface ProgressItem {
section_id: string // 'password_requirements'
status: 'not_started' | 'in_progress' | 'completed'
progress_percentage: number
evidence_ids: string[]
}
function mapProgress(
oldProgress: ProgressItem[],
newTemplate: Template
): ProgressItem[] {
const newSections = newTemplate.locked_content.sections
return newSections.map((section) => {
const oldItem = oldProgress.find((p) => p.section_id === section.id)
if (oldItem) {
// Section exists in both versions β carry forward
return oldItem
} else {
// New section β start from scratch
return {
section_id: section.id,
status: 'not_started',
progress_percentage: 0,
evidence_ids: [],
}
}
})
}
Result¶
v1.0 Progress:
βββ purpose: 100% β
βββ scope: 100% β
βββ password_requirements: 60% β
βββ mfa_requirements: 0%
v1.1 Progress (after mapping):
βββ purpose: 100% β (carried forward)
βββ scope: 100% β (carried forward)
βββ password_requirements: 60% β (carried forward)
βββ mfa_requirements: 0% (carried forward)
βββ biometric_auth: 0% [NEW]
βββ session_management: 0% [NEW]
Security & Multi-Tenancy¶
Row Level Security (RLS)¶
-- Organizations can only see their own policies
CREATE POLICY org_isolation ON customer_policy_instances
FOR ALL
USING (organization_id = current_setting('app.current_organization_id')::UUID);
Data Isolation¶
Organization A Organization B
βββ Policy Instance 1 βββ Policy Instance 10
β Template: Access Control β Template: Access Control
β Version: 1.0 β Version: 1.1
β Config: {their data} β Config: {their data}
βββ Policy Instance 2 βββ Policy Instance 11
Template: Incident Resp Template: BYOD Policy
Key Principle: Template is shared, configuration is isolated
Performance Considerations¶
Update Detection Query¶
-- Optimized with compound index
CREATE INDEX idx_customer_policies_updates
ON customer_policy_instances(template_id, active_template_version);
-- Query executes in <10ms even with 10k policies
SELECT COUNT(*)
FROM customer_policy_instances
WHERE active_template_version < (
SELECT current_version FROM policy_templates WHERE template_id = $1
);
Configuration Storage¶
JSONB Benefits:
- Flexible schema for MVP
- Fast queries with GIN indexes
- Can add fields without migrations
- Supports partial updates
Post-MVP Optimization:
- Could normalize heavily-queried fields
- Add materialized views for analytics
- But MVP JSONB is perfectly fine
MVP vs Post-MVP Features¶
MVP (Essential)¶
β Template versioning (current_version field) β Update detection (version comparison) β Auto-merge logic (reapply configuration) β Dashboard notifications β Simple review UI (changelog + new parameter config) β Progress mapping (section ID matching) β Archive previous versions
Post-MVP (Enhancements)¶
β Sophisticated diff viewer (side-by-side comparison) β Email notifications (just dashboard for MVP) β Policy health dashboard (outdated warnings) β Complex validation rules (parameter dependencies) β "Detach from template" escape hatch β Deprecated section migration workflows β Separate template_versions table (MVP uses single table)
Why This Must Be MVP¶
The Version Debt Trap¶
Without versioning from day one:
Day 1: Customer adopts Access Control Policy
β Stored without template_version field
β Just "Access Control Policy" in database
Day 30: We improve policy content
β Can't tell which version customer has
β Can't provide targeted update
Day 60: 50 customers using various unknown versions
β Data migration nightmare
β Manual customer outreach required
β Trust damage
With versioning from day one:
Day 1: Customer adopts Access Control Policy v1.0
β Stored: template_id + template_version: "1.0"
Day 30: We release v1.1
β Query: WHERE template_version < '1.1'
β Automatic notification to affected customers
Day 60: 50 customers: 30 on v1.0, 20 on v1.1
β Clear audit trail
β Targeted communications
β Smooth update path
We're Selling Managed Compliance¶
Without updates: We're a document generator With updates: We're a compliance platform
The ability to improve policies continuously is core to the value proposition.
Risks & Mitigations¶
Risk 1: Template Content Errors¶
Impact: High - Liability if policy is wrong Mitigation:
- Rigorous internal review process
- External compliance expert review
- Start with well-vetted templates (ISO 27001, E8)
- Version numbers allow rollback
Risk 2: Configuration Model Too Restrictive¶
Impact: Medium - Some customers need more Mitigation:
- MVP: "Company Addenda" provides escape hatch
- Post-MVP: "Detach" for true edge cases
- Target market (SMB/mid-market) rarely needs full customization
Risk 3: Progress Mapping Edge Cases¶
Impact: Low - Incorrectly mapped progress Mitigation:
- Conservative approach (only map exact section ID matches)
- Clear UI showing what was carried forward
- Manual override if needed
Future Enhancements¶
Phase 2: Advanced Features¶
Sophisticated Diff Viewer:
- Side-by-side comparison
- Highlight additions/deletions/modifications
- Jump to changes navigation
Email Notifications:
- Critical updates: Immediate email
- Best practice: Weekly digest
- Customizable notification preferences
Policy Health Dashboard:
- Organization-wide policy compliance score
- "12 of 13 policies up to date (92%)"
- Red/yellow/green indicators
Validation Rules:
- Parameter dependencies ("Biometric auth requires password_length β₯ 14")
- Section prerequisites ("Cloud section requires Network section")
- Custom validation logic
Phase 3: Enterprise Features¶
Detach from Template:
- Full control for edge cases
- Warning about losing updates
- Rich text editor
- Separate "Custom Policies" section
Template Versioning Table:
- Full version history in separate table
- Diff between any two versions
- Rollback capability
Advanced Analytics:
- Update adoption rates
- Time-to-adopt metrics
- Most-skipped updates
- Customer segmentation
Conclusion¶
The configuration-based policy versioning system is foundational to GetCimple's value proposition. By choosing configuration over customization, we've created a system that is:
β Simple - Customers configure, not rewrite β Fast - Updates take minutes, not hours β Reliable - Zero merge conflicts guaranteed β Compliant - Expert-maintained content β Scalable - Supports thousands of customers with one template update
This must be MVP because we're selling managed compliance, not document generation. The alternative (version debt) would create technical debt and trust damage that's difficult to recover from.
The lean MVP scope (simple UI, dashboard-only notifications, basic validation) gets us 80% of the value with 20% of the effort, while establishing the correct data foundation from day one.