Building ProvenanceCode Implementations¶
Want to build a ProvenanceCode validator? This guide helps you create compliant implementations.
Quick Start¶
Minimum Requirements¶
A compliant ProvenanceCode implementation MUST:
- Validate JSON structure against official schemas
- Check decision ID format (hierarchical or legacy, based on config)
- Verify required fields (see spec)
- Support enforcement presets (light, standard, regulated)
- Provide clear feedback to users
- Read and validate config (
provenance/config.json) for ID format settings
Recommended Features¶
Your implementation SHOULD:
- Post-validation results as comments (if on PR platforms)
- Create status checks (pass/fail indicators)
- Link to decision files for easy access
- Suggest fixes when validation fails
- Support all file types (decisions, specs, risks, mistakes)
Optional Enhancements¶
Consider adding:
- Auto-draft creation for missing decisions
- Web UI for browsing decisions
- Analytics and reporting
- Cross-repository validation
- Integration with issue trackers
- IDE plugins
Architecture Patterns¶
Pattern 1: CI/CD Validator¶
Use Case: Validate on pull requests in CI/CD pipelines
Flow:
1. PR opened/updated
2. CI job triggered
3. Validator fetches changed files
4. Check enforcement rules
5. Validate required artifacts
6. Post results & status
Examples: GitHub Actions, GitLab CI, CircleCI
Pros: Simple, no hosting, free
Cons: Limited features, runs after push
Pattern 2: Webhook-Based App¶
Use Case: Real-time validation with advanced features
Flow:
1. PR opened/updated → Webhook sent
2. Server receives webhook
3. Fetch repo contents via API
4. Run validation
5. Post results via API
6. Store state (optional)
Examples: GitHub Apps, GitLab Apps, Bitbucket Apps
Pros: Real-time, advanced features, proactive
Cons: Requires hosting, more complex
Pattern 3: CLI Tool¶
Use Case: Local validation, pre-commit hooks
Flow:
1. Developer runs command
2. Tool scans local files
3. Validates structure
4. Prints results
5. Exit code (0 = pass)
Examples: provenancecode-validate, pre-commit hooks
Pros: Fast feedback, no network
Cons: Requires local install, no enforcement
Pattern 4: IDE Extension¶
Use Case: Real-time feedback while writing
Flow:
1. Developer edits decision file
2. Extension watches file
3. Validates on save
4. Shows inline errors
5. Offers quick fixes
Examples: VS Code extensions, JetBrains plugins
Pros: Best DX, immediate feedback
Cons: Per-IDE development, limited scope
Core Validation Logic¶
1. Load Configuration¶
async function loadConfig(repoPath) {
const configPath = `${repoPath}/provenance/config.json`;
const config = JSON.parse(await readFile(configPath));
// Merge with defaults
return {
enforcement: config.enforcement || 'standard',
requireDecisionOnPaths: config.requireDecisionOnPaths || [],
requireDecisionOnLabels: config.requireDecisionOnLabels || [],
...config
};
}
2. Scan for Artifacts¶
async function scanDecisions(repoPath, config) {
const decisionsPath = `${repoPath}/provenance/decisions`;
const dirs = await listDirectories(decisionsPath);
const decisions = [];
for (const dir of dirs) {
// Validate ID format based on config
if (!validateIdFormat(dir, config)) continue;
const jsonPath = `${decisionsPath}/${dir}/decision.json`;
if (await fileExists(jsonPath)) {
const data = JSON.parse(await readFile(jsonPath));
decisions.push({ id: dir, path: jsonPath, data });
}
}
return decisions;
}
function validateIdFormat(id, config) {
const style = config.id_format?.style || 'legacy';
if (style === 'legacy') {
// Legacy format: DEC-000001 (6 digits)
return /^DEC-\d{6}$/.test(id);
}
if (style === 'hierarchical') {
const requireSubproject = config.id_format?.require_subproject || false;
if (requireSubproject) {
// Full hierarchical: DEC-PROJECT-SUBPROJECT-0000001
return /^DEC-[A-Z0-9]{2,6}-[A-Z0-9]{2,6}-\d{7}$/.test(id);
} else {
// Hierarchical with optional subproject
return /^DEC-[A-Z0-9]{2,6}-([A-Z0-9]{2,6}-)?(\d{7})$/.test(id);
}
}
return false;
}
3. Validate Structure¶
function validateDecision(decision) {
const errors = [];
const warnings = [];
// Required fields
const required = [
'schema', 'id', 'title', 'version',
'lifecycle', 'timestamps', 'actors',
'outcome', 'rationale', 'risk'
];
for (const field of required) {
if (!decision[field]) {
errors.push(`Missing required field: ${field}`);
}
}
// ID format (validate based on config)
if (!validateIdFormat(decision.id, config)) {
errors.push('Invalid ID format (check provenance/config.json for expected format)');
}
// Schema version
if (decision.schema !== 'provenancecode.decision.v1') {
errors.push('Invalid schema version');
}
// Lifecycle state
const validStates = ['draft', 'proposed', 'accepted', 'rejected', 'superseded'];
if (!validStates.includes(decision.lifecycle?.state)) {
errors.push('Invalid lifecycle state');
}
// Timestamps
if (!isValidISO8601(decision.timestamps?.created_at)) {
errors.push('Invalid created_at timestamp');
}
// Risk level
const validRiskLevels = ['low', 'medium', 'high', 'critical'];
if (!validRiskLevels.includes(decision.risk?.level)) {
errors.push('Invalid risk level');
}
// Warnings
if (!decision.options || decision.options.length === 0) {
warnings.push('No alternatives listed in options');
}
if (!decision.problem) {
warnings.push('No problem statement provided');
}
return { valid: errors.length === 0, errors, warnings };
}
4. Apply Enforcement Rules¶
function checkEnforcement(config, changedFiles, prLabels, decisions) {
const enforcement = config.enforcement;
let required = false;
// Check paths
for (const file of changedFiles) {
for (const pattern of config.requireDecisionOnPaths) {
if (file.startsWith(pattern)) {
required = true;
break;
}
}
}
// Check labels
for (const label of prLabels) {
if (config.requireDecisionOnLabels.includes(label)) {
required = true;
break;
}
}
if (!required) {
return { required: false, blocking: false };
}
// Apply preset rules
if (enforcement === 'light') {
return { required: true, blocking: false };
}
if (enforcement === 'standard') {
// Block on specific labels only
const blockingLabels = ['breaking-change', 'architecture', 'security'];
const hasBlockingLabel = prLabels.some(l => blockingLabels.includes(l));
return { required: true, blocking: hasBlockingLabel };
}
if (enforcement === 'regulated') {
return { required: true, blocking: true };
}
return { required: false, blocking: false };
}
5. Generate Results¶
function generateResults(decisions, enforcement, validations) {
const passed = validations.every(v => v.valid);
let status = 'success';
let message = 'All checks passed';
if (!passed) {
if (enforcement.blocking) {
status = 'failure';
message = 'Validation failed';
} else {
status = 'warning';
message = 'Validation issues found';
}
}
return {
status,
message,
summary: {
decisions_found: decisions.length,
decisions_required: enforcement.required ? 1 : 0,
errors: validations.flatMap(v => v.errors),
warnings: validations.flatMap(v => v.warnings)
},
details: validations
};
}
JSON Schemas¶
Use these schemas for validation:
Decision Schema: provenancecode.decision.v1
Available at: https://schemas.provenancecode.org/v1/decision.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"schema", "id", "title", "version", "lifecycle",
"timestamps", "actors", "outcome", "rationale", "risk"
],
"properties": {
"schema": {
"type": "string",
"const": "provenancecode.decision.v1"
},
"id": {
"type": "string",
"pattern": "^DEC-\\d{6}$"
},
"title": {
"type": "string",
"maxLength": 120
},
"version": {
"type": "integer",
"minimum": 1
},
"lifecycle": {
"type": "object",
"required": ["state"],
"properties": {
"state": {
"enum": ["draft", "proposed", "accepted", "rejected", "superseded"]
},
"supersedes": { "type": "string" },
"superseded_by": { "type": "string" }
}
},
"timestamps": {
"type": "object",
"required": ["created_at"],
"properties": {
"created_at": { "type": "string", "format": "date-time" },
"accepted_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" },
"expires_at": { "type": "string", "format": "date-time" }
}
},
"actors": {
"type": "object",
"required": ["author"],
"properties": {
"author": { "type": "string" },
"approver": { "type": "string" },
"bot": { "type": "string" },
"reviewers": { "type": "array", "items": { "type": "string" } }
}
},
"outcome": { "type": "string" },
"rationale": { "type": "string" },
"risk": {
"type": "object",
"required": ["level"],
"properties": {
"level": { "enum": ["low", "medium", "high", "critical"] },
"description": { "type": "string" },
"acceptance": { "type": "string" },
"mitigations": { "type": "array", "items": { "type": "string" } }
}
}
}
}
Testing Your Implementation¶
Test Cases¶
Create test repositories with these scenarios:
✅ Valid Cases: 1. Minimal valid decision 2. Complete decision with all fields 3. Multiple decisions in PR 4. Decision with links and references
❌ Invalid Cases: 1. Missing required fields 2. Invalid ID format 3. Invalid lifecycle state 4. Invalid timestamp format 5. Missing decision when required
⚠️ Edge Cases: 1. Empty decisions directory 2. Superseded decisions 3. Multiple versions of same decision 4. Circular supersession links
Compliance Checklist¶
Before releasing, verify:
- [ ] Validates against official JSON schema
- [ ] Checks all required fields
- [ ] Validates ID format (hierarchical or legacy based on config)
- [ ] Supports all enforcement presets
- [ ] Handles missing files gracefully
- [ ] Provides clear error messages
- [ ] Links back to ProvenanceCode standard
- [ ] Has documentation for users
- [ ] Handles superseded decisions
- [ ] Validates timestamp formats
- [ ] Checks lifecycle states
Platform-Specific Guides¶
GitHub Actions¶
See GitHub Actions implementation guide
GitLab CI¶
provenancecode-validation:
stage: test
script:
- npm install -g @provenancecode/validator
- provenancecode-validate
only:
- merge_requests
CircleCI¶
version: 2.1
jobs:
validate:
docker:
- image: node:18
steps:
- checkout
- run: npm install -g @provenancecode/validator
- run: provenancecode-validate
workflows:
pr-validation:
jobs:
- validate
Bitbucket Pipelines¶
pipelines:
pull-requests:
'**':
- step:
name: ProvenanceCode Validation
script:
- npm install -g @provenancecode/validator
- provenancecode-validate
Publishing Your Implementation¶
1. Document It¶
Create clear documentation: - Installation instructions - Configuration options - Usage examples - Troubleshooting guide
2. License It¶
Choose an appropriate license: - Open source: Apache 2.0, MIT, GPL, etc. - Commercial: Your choice - Dual license: Offer both
3. Respect the Trademark¶
ProvenanceCode™ is a trademark of EmbankAI (KDDLC AI Solutions SL).
When naming your implementation: - ✅ Good: "GitLab ProvenanceCode Validator", "ProvenanceCode Tools for Jenkins" - ❌ Avoid: "ProvenanceCode Enterprise", "Official ProvenanceCode"
You MAY use "ProvenanceCode" to describe compatibility. You MUST NOT imply official endorsement.
See Trademark Policy for full guidelines.
4. Share It¶
- Submit PR to add it to Implementations
- Tag it:
provenancecode-implementation - Tweet with #ProvenanceCode
- Post on relevant forums
4. Get Feedback¶
- Enable GitHub Issues
- Join community discussions
- Iterate based on user feedback
Support¶
Questions?¶
- Spec questions: GitHub Issues
- Implementation help: Discussions
- Community: Join our community channels
Found an Ambiguity?¶
If the specification is unclear, please: 1. Open an issue describing the ambiguity 2. Propose clarification 3. We'll update the spec for everyone
Ready to build? Start with the Standard Overview and DEO Specification.