Skip to main content

ADR-LMS-005: Certificate Generation & Verification System

Status: Proposed Date: 2025-12-11 Phase: Phase 2 - Core LMS Infrastructure Deciders: Hal Casteel (Founder/CEO/CTO), CODITECT Core Team Technical Story: Enable generation, verification, and sharing of verifiable digital certificates for CODITECT learning achievements


Context and Problem Statement

The current CODITECT training system uses ASCII art badges in markdown format:

╔════════════════════════════════════════╗
║ CODITECT CERTIFIED OPERATOR ║
║ Level: _________________________ ║
║ Date: __________________________ ║
╚════════════════════════════════════════╝

This approach has limitations:

  1. No Verification - Anyone can copy/edit the ASCII badge
  2. No Digital Signature - No cryptographic proof of authenticity
  3. No Shareable Format - Can't be displayed on LinkedIn/portfolio
  4. No Expiration Tracking - No validity period management
  5. No Revocation - Can't invalidate compromised certificates
  6. No Third-Party Verification - External parties can't verify claims

The Problem: How do we create a robust, verifiable certificate system that provides genuine credential validation while remaining accessible in CLI-first workflows?


Decision Drivers

Technical Requirements

  • R1: Cryptographically signed certificates (digital signatures)
  • R2: Unique verification codes with public lookup
  • R3: PDF generation with embedded digital signature
  • R4: JSON-LD/Open Badges 3.0 format for interoperability
  • R5: QR codes linking to verification page
  • R6: Expiration and renewal tracking
  • R7: Revocation capability with reason tracking

User Experience Goals

  • UX1: One-click certificate download (PDF, PNG)
  • UX2: Share to LinkedIn with verification link
  • UX3: Public verification page (no login required)
  • UX4: Certificate portfolio view
  • UX5: Renewal reminders before expiration

Business Requirements

  • B1: Branded certificate templates
  • B2: Organization-specific certificates
  • B3: Bulk certificate issuance for teams
  • B4: Certificate analytics (views, verifications)

Compliance Requirements

  • C1: Open Badges 3.0 standard compliance
  • C2: European Digital Credentials (future)
  • C3: Blockchain anchoring option (future)

Decision Outcome

Chosen Solution: Implement a comprehensive certificate system with cryptographic signing, multiple export formats, Open Badges 3.0 compliance, and public verification API.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│ Certificate Generation Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Achievement │────▶│ Certificate │────▶│ Digital │ │
│ │ Completion │ │ Template │ │ Signing │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Export Formats │ │
│ │ │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────────────┐ │ │
│ │ │ PDF │ │ PNG │ │ JSON │ │ Open Badges 3.0│ │ │
│ │ │ (A4) │ │ (Social│ │ (API) │ │ (JSON-LD) │ │ │
│ │ │ │ │ Share)│ │ │ │ │ │ │
│ │ └────────┘ └────────┘ └────────┘ └────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Verification Layer │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ Public │ │ QR Code │ │ Embedded │ │ │
│ │ │ Web Page │ │ Scanner │ │ JSON-LD │ │ │
│ │ │ /verify/ │ │ │ │ Verification │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

Implementation Details

1. Database Schema

-- Certificate definitions (templates)
CREATE TABLE cert_definitions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cert_key TEXT UNIQUE NOT NULL, -- coditect-operator-expert
title TEXT NOT NULL, -- CODITECT Expert Operator
description TEXT,
cert_level TEXT, -- foundational, professional, expert
issuer_name TEXT DEFAULT 'CODITECT by AZ1.AI',
issuer_url TEXT DEFAULT 'https://coditect.ai',
validity_months INTEGER, -- NULL = never expires
renewal_available BOOLEAN DEFAULT 1,

-- Requirements
required_path_id INTEGER, -- Learning path to complete
required_quiz_id INTEGER, -- Final exam
required_score INTEGER DEFAULT 80, -- Minimum passing score
required_modules TEXT, -- JSON array of module IDs

-- Template
template_pdf TEXT, -- Path to PDF template
template_png TEXT, -- Path to PNG template
accent_color TEXT DEFAULT '#2563EB',
logo_url TEXT,

-- Metadata
skills_json TEXT, -- JSON array of skills validated
badge_id INTEGER, -- Associated badge
is_active BOOLEAN DEFAULT 1,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,

FOREIGN KEY (required_path_id) REFERENCES learning_paths(id) ON DELETE SET NULL,
FOREIGN KEY (required_quiz_id) REFERENCES learning_quizzes(id) ON DELETE SET NULL,
FOREIGN KEY (badge_id) REFERENCES learning_badges(id) ON DELETE SET NULL
);

CREATE INDEX idx_cert_definitions_key ON cert_definitions(cert_key);
CREATE INDEX idx_cert_definitions_level ON cert_definitions(cert_level);

-- Issued certificates
CREATE TABLE cert_issued (
id INTEGER PRIMARY KEY AUTOINCREMENT,
certificate_id INTEGER NOT NULL,
user_id TEXT NOT NULL,

-- Verification
verification_code TEXT UNIQUE NOT NULL, -- UUID for public verification
verification_url TEXT NOT NULL, -- Full URL to verify page
qr_code_data TEXT, -- Base64 QR code image

-- Digital signature
signature TEXT NOT NULL, -- RSA/ECDSA signature
signature_algorithm TEXT DEFAULT 'RS256',
public_key_id TEXT, -- Key used for signing

-- Achievement data
final_score INTEGER,
completion_date TEXT NOT NULL,
quiz_attempt_id INTEGER,

-- Validity
issued_at TEXT DEFAULT CURRENT_TIMESTAMP,
expires_at TEXT,
renewed_from_id INTEGER, -- Previous cert if renewal

-- Status
is_revoked BOOLEAN DEFAULT 0,
revoked_at TEXT,
revoked_reason TEXT,
revoked_by TEXT,

-- Export cache
pdf_url TEXT,
png_url TEXT,
json_ld TEXT, -- Open Badges 3.0 JSON-LD

-- Analytics
view_count INTEGER DEFAULT 0,
verify_count INTEGER DEFAULT 0,
share_count INTEGER DEFAULT 0,
last_verified_at TEXT,

-- Metadata
metadata TEXT, -- JSON additional data
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,

FOREIGN KEY (certificate_id) REFERENCES cert_definitions(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES auth_users(user_id) ON DELETE CASCADE,
FOREIGN KEY (quiz_attempt_id) REFERENCES learning_quiz_attempts(id) ON DELETE SET NULL,
FOREIGN KEY (renewed_from_id) REFERENCES cert_issued(id) ON DELETE SET NULL
);

CREATE INDEX idx_cert_issued_user ON cert_issued(user_id);
CREATE INDEX idx_cert_issued_verification ON cert_issued(verification_code);
CREATE INDEX idx_cert_issued_expires ON cert_issued(expires_at);

-- Certificate verification log
CREATE TABLE cert_verification_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cert_issued_id INTEGER NOT NULL,
verification_code TEXT NOT NULL,
verifier_ip TEXT,
verifier_user_agent TEXT,
verification_method TEXT, -- web, api, qr_scan
verification_result TEXT, -- valid, expired, revoked, not_found
created_at TEXT DEFAULT CURRENT_TIMESTAMP,

FOREIGN KEY (cert_issued_id) REFERENCES cert_issued(id) ON DELETE CASCADE
);

CREATE INDEX idx_cert_verify_log_cert ON cert_verification_log(cert_issued_id);
CREATE INDEX idx_cert_verify_log_date ON cert_verification_log(created_at);

-- Signing keys (for key rotation)
CREATE TABLE cert_signing_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_id TEXT UNIQUE NOT NULL, -- kid for JWT/JWK
algorithm TEXT NOT NULL, -- RS256, ES256
public_key TEXT NOT NULL, -- PEM format
private_key_encrypted TEXT NOT NULL, -- Encrypted PEM
is_active BOOLEAN DEFAULT 1,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
expires_at TEXT,
revoked_at TEXT
);

2. Certificate Generation

import hashlib
import json
import uuid
from datetime import datetime, timedelta
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
import qrcode
import base64

def issue_certificate(user_id: str, cert_key: str, quiz_attempt_id: int = None) -> dict:
"""
Issue a certificate to a user who has met all requirements.
"""
# Get certificate definition
cert_def = get_cert_definition(cert_key)
if not cert_def:
raise ValueError(f"Certificate definition not found: {cert_key}")

# Verify user exists and is authenticated
user = get_authenticated_user(user_id)
if not user:
raise ValueError("User not authenticated")

# Check requirements
if cert_def['required_path_id']:
enrollment = get_path_enrollment(user_id, cert_def['required_path_id'])
if not enrollment or not enrollment['completed_at']:
raise ValueError("Learning path not completed")

if cert_def['required_quiz_id']:
if not quiz_attempt_id:
raise ValueError("Quiz attempt required")
attempt = get_quiz_attempt(quiz_attempt_id)
if not attempt or not attempt['passed']:
raise ValueError("Quiz not passed")
if attempt['score'] < cert_def['required_score']:
raise ValueError(f"Score {attempt['score']} below required {cert_def['required_score']}")

# Generate verification code
verification_code = str(uuid.uuid4())
verification_url = f"https://coditect.ai/verify/{verification_code}"

# Calculate expiration
expires_at = None
if cert_def['validity_months']:
expires_at = (datetime.now() + timedelta(days=cert_def['validity_months'] * 30)).isoformat()

# Create certificate data payload
cert_data = {
"type": "CODITECT_CERTIFICATE",
"version": "1.0",
"certificate": {
"id": verification_code,
"title": cert_def['title'],
"level": cert_def['cert_level'],
"description": cert_def['description']
},
"recipient": {
"id": user['user_id'],
"name": user['display_name'],
"email": user['email']
},
"issuer": {
"name": cert_def['issuer_name'],
"url": cert_def['issuer_url']
},
"achievement": {
"score": quiz_attempt_id and get_quiz_attempt(quiz_attempt_id)['score'],
"completion_date": datetime.now().isoformat(),
"skills": json.loads(cert_def['skills_json'] or '[]')
},
"validity": {
"issued_at": datetime.now().isoformat(),
"expires_at": expires_at
}
}

# Sign certificate
signature, key_id = sign_certificate(cert_data)

# Generate QR code
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(verification_url)
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white")
qr_buffer = io.BytesIO()
qr_img.save(qr_buffer, format='PNG')
qr_code_data = base64.b64encode(qr_buffer.getvalue()).decode()

# Generate Open Badges 3.0 JSON-LD
json_ld = generate_open_badges_json_ld(cert_def, user, verification_code, cert_data)

# Insert certificate record
db.execute("""
INSERT INTO cert_issued (
certificate_id, user_id, verification_code, verification_url,
qr_code_data, signature, signature_algorithm, public_key_id,
final_score, completion_date, quiz_attempt_id, expires_at, json_ld
) VALUES (?, ?, ?, ?, ?, ?, 'RS256', ?, ?, ?, ?, ?, ?)
""", (
cert_def['id'], user_id, verification_code, verification_url,
qr_code_data, signature, key_id,
cert_data['achievement']['score'],
cert_data['achievement']['completion_date'],
quiz_attempt_id, expires_at, json.dumps(json_ld)
))

cert_id = db.lastrowid

# Generate PDF and PNG
pdf_path = generate_certificate_pdf(cert_id, cert_def, user, cert_data, qr_code_data)
png_path = generate_certificate_png(cert_id, cert_def, user, cert_data, qr_code_data)

# Update with file paths
db.execute("""
UPDATE cert_issued SET pdf_url = ?, png_url = ? WHERE id = ?
""", (pdf_path, png_path, cert_id))

# Award associated badge
if cert_def['badge_id']:
award_badge(user_id, cert_def['badge_id'])

return {
"certificate_id": cert_id,
"verification_code": verification_code,
"verification_url": verification_url,
"pdf_url": pdf_path,
"png_url": png_path,
"expires_at": expires_at
}


def sign_certificate(cert_data: dict) -> tuple:
"""
Digitally sign certificate data using RSA.
"""
# Get active signing key
key_record = db.execute("""
SELECT * FROM cert_signing_keys WHERE is_active = 1 ORDER BY created_at DESC LIMIT 1
""").fetchone()

if not key_record:
raise ValueError("No active signing key")

# Decrypt private key
private_key = serialization.load_pem_private_key(
decrypt(key_record['private_key_encrypted']).encode(),
password=None
)

# Create canonical JSON
canonical_json = json.dumps(cert_data, sort_keys=True, separators=(',', ':'))

# Sign with RSA-SHA256
signature = private_key.sign(
canonical_json.encode(),
padding.PKCS1v15(),
hashes.SHA256()
)

return base64.b64encode(signature).decode(), key_record['key_id']


def generate_open_badges_json_ld(cert_def: dict, user: dict,
verification_code: str, cert_data: dict) -> dict:
"""
Generate Open Badges 3.0 compliant JSON-LD credential.
"""
return {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": f"urn:uuid:{verification_code}",
"type": ["VerifiableCredential", "OpenBadgeCredential"],
"issuer": {
"id": "https://coditect.ai/issuer",
"type": "Profile",
"name": cert_def['issuer_name'],
"url": cert_def['issuer_url'],
"image": {
"id": cert_def['logo_url'] or "https://coditect.ai/logo.png",
"type": "Image"
}
},
"issuanceDate": cert_data['validity']['issued_at'],
"expirationDate": cert_data['validity']['expires_at'],
"credentialSubject": {
"id": f"urn:uuid:{user['user_id']}",
"type": "AchievementSubject",
"name": user['display_name'],
"achievement": {
"id": f"https://coditect.ai/achievements/{cert_def['cert_key']}",
"type": "Achievement",
"name": cert_def['title'],
"description": cert_def['description'],
"criteria": {
"narrative": f"Complete the {cert_def['title']} learning path and pass the certification exam with a score of {cert_def['required_score']}% or higher."
},
"achievementType": "Certificate",
"image": {
"id": f"https://coditect.ai/badges/{cert_def['cert_key']}.png",
"type": "Image"
}
},
"result": [
{
"type": "Result",
"resultDescription": "Final Exam Score",
"value": str(cert_data['achievement']['score'])
}
]
},
"proof": {
"type": "RsaSignature2018",
"created": cert_data['validity']['issued_at'],
"verificationMethod": "https://coditect.ai/.well-known/jwks.json",
"proofPurpose": "assertionMethod"
}
}


def generate_certificate_pdf(cert_id: int, cert_def: dict, user: dict,
cert_data: dict, qr_code_data: str) -> str:
"""
Generate PDF certificate with digital signature.
"""
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.units import inch
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor

output_path = f"certificates/{cert_id}.pdf"
c = canvas.Canvas(output_path, pagesize=landscape(A4))
width, height = landscape(A4)

# Background
c.setFillColor(HexColor('#FAFAFA'))
c.rect(0, 0, width, height, fill=1)

# Border
c.setStrokeColor(HexColor(cert_def['accent_color']))
c.setLineWidth(3)
c.rect(30, 30, width - 60, height - 60)

# Title
c.setFont("Helvetica-Bold", 36)
c.setFillColor(HexColor('#1F2937'))
c.drawCentredString(width / 2, height - 80, "CERTIFICATE OF ACHIEVEMENT")

# Certificate Name
c.setFont("Helvetica-Bold", 28)
c.setFillColor(HexColor(cert_def['accent_color']))
c.drawCentredString(width / 2, height - 130, cert_def['title'])

# Presented to
c.setFont("Helvetica", 16)
c.setFillColor(HexColor('#6B7280'))
c.drawCentredString(width / 2, height - 180, "This is to certify that")

# Recipient Name
c.setFont("Helvetica-Bold", 32)
c.setFillColor(HexColor('#1F2937'))
c.drawCentredString(width / 2, height - 220, user['display_name'])

# Achievement text
c.setFont("Helvetica", 14)
c.setFillColor(HexColor('#6B7280'))
text = f"has successfully completed the {cert_def['title']} certification"
c.drawCentredString(width / 2, height - 260, text)

if cert_data['achievement']['score']:
c.drawCentredString(width / 2, height - 280,
f"with a score of {cert_data['achievement']['score']}%")

# Date
c.setFont("Helvetica", 12)
completion_date = datetime.fromisoformat(cert_data['achievement']['completion_date'])
c.drawCentredString(width / 2, height - 320,
f"Issued on {completion_date.strftime('%B %d, %Y')}")

# QR Code
qr_img = base64.b64decode(qr_code_data)
qr_path = f"/tmp/qr_{cert_id}.png"
with open(qr_path, 'wb') as f:
f.write(qr_img)
c.drawImage(qr_path, 50, 50, width=80, height=80)

# Verification URL
c.setFont("Helvetica", 8)
c.drawString(50, 35, f"Verify: coditect.ai/verify/{cert_data['certificate']['id'][:8]}...")

# Issuer signature area
c.setFont("Helvetica", 10)
c.drawString(width - 200, 80, "Hal Casteel")
c.setFont("Helvetica-Oblique", 8)
c.drawString(width - 200, 65, "Founder & CEO, AZ1.AI Inc")
c.line(width - 200, 90, width - 80, 90)

# Certificate ID
c.setFont("Helvetica", 8)
c.setFillColor(HexColor('#9CA3AF'))
c.drawString(width - 200, 35, f"Certificate ID: {cert_data['certificate']['id'][:16]}...")

c.save()
return output_path

3. Verification System

def verify_certificate(verification_code: str) -> dict:
"""
Public verification endpoint - no authentication required.
"""
# Lookup certificate
cert = db.execute("""
SELECT ci.*, cd.title, cd.description, cd.cert_level, cd.issuer_name,
au.display_name, au.email
FROM cert_issued ci
JOIN cert_definitions cd ON ci.certificate_id = cd.id
JOIN auth_users au ON ci.user_id = au.user_id
WHERE ci.verification_code = ?
""", (verification_code,)).fetchone()

if not cert:
log_verification(None, verification_code, 'not_found')
return {
"valid": False,
"status": "not_found",
"message": "Certificate not found"
}

# Check if revoked
if cert['is_revoked']:
log_verification(cert['id'], verification_code, 'revoked')
return {
"valid": False,
"status": "revoked",
"message": "Certificate has been revoked",
"revoked_at": cert['revoked_at'],
"revoked_reason": cert['revoked_reason']
}

# Check expiration
if cert['expires_at']:
expires = datetime.fromisoformat(cert['expires_at'])
if expires < datetime.now():
log_verification(cert['id'], verification_code, 'expired')
return {
"valid": False,
"status": "expired",
"message": "Certificate has expired",
"expired_at": cert['expires_at'],
"renewal_available": True
}

# Verify digital signature
signature_valid = verify_signature(cert)

if not signature_valid:
log_verification(cert['id'], verification_code, 'invalid_signature')
return {
"valid": False,
"status": "invalid_signature",
"message": "Certificate signature verification failed"
}

# Update verification count
db.execute("""
UPDATE cert_issued
SET verify_count = verify_count + 1, last_verified_at = ?
WHERE id = ?
""", (datetime.now().isoformat(), cert['id']))

log_verification(cert['id'], verification_code, 'valid')

return {
"valid": True,
"status": "valid",
"certificate": {
"title": cert['title'],
"level": cert['cert_level'],
"description": cert['description']
},
"recipient": {
"name": cert['display_name'],
"email_hash": hashlib.sha256(cert['email'].encode()).hexdigest()[:16]
},
"issuer": {
"name": cert['issuer_name'],
"verified": True
},
"dates": {
"issued_at": cert['issued_at'],
"expires_at": cert['expires_at'],
"completion_date": cert['completion_date']
},
"achievement": {
"score": cert['final_score']
},
"verification": {
"code": verification_code,
"verified_at": datetime.now().isoformat(),
"signature_valid": True
}
}


def verify_signature(cert: dict) -> bool:
"""
Verify the digital signature on a certificate.
"""
# Get signing key
key_record = db.execute("""
SELECT * FROM cert_signing_keys WHERE key_id = ?
""", (cert['public_key_id'],)).fetchone()

if not key_record:
return False

# Load public key
public_key = serialization.load_pem_public_key(
key_record['public_key'].encode()
)

# Reconstruct certificate data
cert_data = {
"type": "CODITECT_CERTIFICATE",
"version": "1.0",
# ... reconstruct from cert record
}

canonical_json = json.dumps(cert_data, sort_keys=True, separators=(',', ':'))

try:
public_key.verify(
base64.b64decode(cert['signature']),
canonical_json.encode(),
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except Exception:
return False

4. CLI Commands

# Certificate management
/certs # List my certificates
/certs --available # Certificates I can earn
/certs --download VERIFICATION_CODE # Download PDF
/certs --verify VERIFICATION_CODE # Verify a certificate
/certs --share VERIFICATION_CODE # Get share links

# Certificate details
/certs show VERIFICATION_CODE # Show certificate details
/certs export VERIFICATION_CODE --format pdf|png|json

# LinkedIn sharing
/certs linkedin VERIFICATION_CODE # Generate LinkedIn add link

# Renewal
/certs renew VERIFICATION_CODE # Renew expiring certificate

# Admin commands (instructors/admins)
/certs issue --user USER_ID --cert CERT_KEY
/certs revoke VERIFICATION_CODE --reason "Reason"
/certs bulk-issue --cert CERT_KEY --users user1,user2,user3

5. Public Verification Page

<!-- /verify/{verification_code} -->
<!DOCTYPE html>
<html>
<head>
<title>Certificate Verification | CODITECT</title>
<meta property="og:title" content="CODITECT Certificate Verification">
<meta property="og:description" content="Verify CODITECT learning credentials">
</head>
<body>
<div class="verification-card">
<!-- Valid Certificate -->
<div class="status valid">
<span class="icon"></span>
<h2>Certificate Verified</h2>
</div>

<div class="certificate-details">
<h3>{{ certificate.title }}</h3>
<p class="recipient">Awarded to <strong>{{ recipient.name }}</strong></p>
<p class="date">Issued on {{ dates.issued_at | date }}</p>

<div class="score" v-if="achievement.score">
Score: {{ achievement.score }}%
</div>

<div class="issuer">
<img src="/logo.png" alt="CODITECT">
<span>Issued by {{ issuer.name }}</span>
</div>
</div>

<div class="verification-meta">
<p>Verification Code: {{ verification.code }}</p>
<p>Verified at: {{ verification.verified_at }}</p>
<p>Signature: <span class="signature-valid">Valid</span></p>
</div>

<div class="actions">
<a href="/verify/{{ verification.code }}.pdf">Download PDF</a>
<a href="/verify/{{ verification.code }}.json">View JSON-LD</a>
</div>
</div>
</body>
</html>

Seed Data: Default Certificate Definitions

INSERT INTO cert_definitions (cert_key, title, description, cert_level, required_score, validity_months, skills_json) VALUES
('coditect-foundation', 'CODITECT Foundation Operator',
'Demonstrates foundational knowledge of CODITECT framework, agent invocation, and basic workflows.',
'foundational', 70, NULL,
'["Task Tool Pattern", "Agent Invocation", "Environment Setup", "Basic Commands"]'),

('coditect-business', 'CODITECT Business Operator',
'Demonstrates proficiency in business discovery, market research, and strategic planning using CODITECT.',
'foundational', 75, NULL,
'["Market Research", "ICP Creation", "Pricing Strategy", "GTM Planning", "Competitive Analysis"]'),

('coditect-technical', 'CODITECT Technical Operator',
'Demonstrates proficiency in technical specification, architecture design, and documentation using CODITECT.',
'professional', 80, 24,
'["C4 Architecture", "Database Design", "API Specification", "ADR Creation", "System Design"]'),

('coditect-expert', 'CODITECT Expert Operator',
'Demonstrates advanced proficiency across all CODITECT capabilities including orchestration and custom workflows.',
'expert', 85, 24,
'["Multi-Agent Orchestration", "Custom Workflows", "Session Management", "Token Optimization", "Advanced Git"]'),

('coditect-architect', 'CODITECT Architect',
'Demonstrates mastery of CODITECT framework extension, team leadership, and enterprise deployment.',
'expert', 90, 12,
'["Custom Agent Creation", "Framework Extension", "Team Training", "Enterprise Patterns", "Contribution"]');

Consequences

Positive

  • P1: Verifiable credentials with cryptographic proof
  • P2: Industry-standard Open Badges 3.0 compliance
  • P3: Professional PDF certificates for sharing
  • P4: Public verification without login required
  • P5: Revocation and expiration management

Negative

  • N1: Key management complexity
  • N2: Storage requirements for PDFs
  • N3: External dependency on crypto libraries

Risks

  • Risk 1: Signing key compromise
    • Mitigation: Key rotation, HSM option, short key validity
  • Risk 2: Certificate forgery
    • Mitigation: Digital signatures, public verification API


Status: Proposed - Phase 2 Core Infrastructure Last Updated: 2025-12-11 Version: 1.0.0