CipherShift365 — Questionnaire Engine
Component: CipherShift365.Core.Analysis (assessment engine) + REST API endpoints + Blazor portal UI
Model: Versioned assessment templates with transparent scoring
Default template: PQC Readiness Assessment (6 sections, 18 questions)
Target framework: .NET 10.0
What It Does
The Questionnaire Engine is a self-contained assessment system that measures an organization's readiness for post-quantum cryptography migration. It supports versioned templates, four question types, transparent scoring with section-level breakdowns, and actionable recommendations.
Key features:
- Versioned templates — each assessment template has an
Id,Version, andCreatedAttimestamp. Responses record the template version they were submitted against. - Four question types —
SingleChoice(radio),MultiChoice(check-all-that-apply),FreeText(open-ended),Numeric(quantitative). - Transparent scoring — every question has a
MaxScore. Section scores, total scores, and readiness percentages are calculated with a deterministic algorithm. - Recommendations engine — sections scoring below 40% generate "needs significant improvement" items; below 70% generate "moderate — further action recommended."
- API integration — full CRUD via
/v1/questionnaire/*endpoints. - Portal UI — interactive form with radio groups and text inputs, client-side score preview, and post-submission results display.
- Air-gapped — no external survey service, no SaaS dependency, no telemetry.
Why It's Needed (Value Proposition)
| Need | How the questionnaire engine addresses it |
|---|---|
| Standardized readiness assessment | The default pqc-readiness-v1 template covers 6 domains: Regulatory, Estate, Inventory, Data/HNDL, Migration, Governance. Every engagement starts from the same baseline. |
| Objective scoring | Algorithm-based scoring removes subjectivity. Two assessors evaluating the same answers always produce the same score. |
| Audit evidence | Each response is timestamped, attributed to an actor, and retrievable via API. An auditor can trace a readiness percentage back to individual answers. |
| CISO-facing output | Scores, readiness bands (Critical/Early/Moderate/Advanced), and specific recommendations are immediately actionable. |
| Customizable | Organizations can add versioned templates for their own domains. The engine does not hard-code the PQC template — it's a generic assessment system. |
| No vendor lock-in | Templates and responses are plain .NET records. Export them as JSON for any external system. |
Installation
Prerequisites
| Requirement | Minimum |
|---|---|
| .NET SDK | 10.0 (build); runtime: 8.0+ |
| Dependencies | CipherShift365.Core.Runtime (types), CipherShift365.Core.Analysis (engine) |
Package Reference
The assessment engine is part of CipherShift365.Core.Analysis. The types are in CipherShift365.Core.Runtime:
<ProjectReference Include="..\..\Core\CipherShift365.Core.Analysis\CipherShift365.Core.Analysis.csproj" />
No external NuGet packages are required beyond the .NET runtime.
Standalone Usage
# Create a console app referencing the analysis engine
dotnet new console -n QuestionnaireDemo
cd QuestionnaireDemo
dotnet add reference ..\CipherShift365.Core.Analysis\CipherShift365.Core.Analysis.csproj
How to Run / Use
Quick Start — Score the Default Assessment
using CipherShift365.Core.Analysis;
using CipherShift365.Core.Runtime;
var service = new AssessmentService();
// List available templates
foreach (var t in service.GetTemplates())
Console.WriteLine($"{t.Id} v{t.Version}: {t.Title} ({t.Sections.Sum(s => s.Questions.Count)} questions)");
// Submit a response
var response = service.Submit("pqc-readiness-v1", "Alice (Security Architect)",
new[]
{
new QuestionAnswer("q1.1", "Yes, fully applicable", 15),
new QuestionAnswer("q1.2", "CNSA 2.0 compliance mandate", 10),
new QuestionAnswer("q1.3", "2030 (key establishment deadline)", 10),
// ... answers for all 18 questions
}.AsReadOnly());
// Score
var score = service.Score(response);
Console.WriteLine($"Readiness: {score.ReadinessPercent:F1}%");
Console.WriteLine($"Summary: {score.Summary}");
foreach (var rec in score.Recommendations ?? Array.Empty<string>())
Console.WriteLine($" → {rec}");
Via the REST API
TOKEN=$(curl -s -X POST http://localhost:5000/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"clientId":"admin-001","secret":"any"}' | jq -r .token)
# List templates
curl -s http://localhost:5000/v1/questionnaire/templates \
-H "Authorization: Bearer $TOKEN" | jq .
# Get full template
curl -s http://localhost:5000/v1/questionnaire/templates/pqc-readiness-v1 \
-H "Authorization: Bearer $TOKEN" | jq .
# Submit
curl -s -X POST http://localhost:5000/v1/questionnaire/submit \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"templateId": "pqc-readiness-v1",
"answers": [
{"questionId":"q1.1","value":"Yes, fully applicable","score":15},
{"questionId":"q1.2","value":"CNSA 2.0 compliance mandate","score":10}
]
}' | jq .
Via the Blazor Portal
Navigate to /questionnaire (requires ConsultantAdmin or Auditor role). The page:
- Loads the
pqc-readiness-v1template. - Renders each section as a titled card with question inputs.
- Single-choice questions display as radio button groups.
- On submit, POSTs answers to the API and displays the scored result.
User Manual
Question Types
| Type | Input | Scoring | Example |
|---|---|---|---|
SingleChoice |
Radio button group | Positional: first option = highest score, last option = lowest. Score = (1 - idx/total) * MaxScore, minimum 1. |
q1.1: CNSA applicability (4 options, max 15 pts) |
MultiChoice |
Text input (free-form) | Manual: user provides text; basic heuristic (length > 10 chars = 3 pts, else 1 pt) | q2.3: .NET versions deployed |
FreeText |
Text input | Manual: same basic heuristic | q3.3: Automated discovery tool |
Numeric |
Number input | Direct numeric value | (Not used in default template, available for custom templates) |
Scoring for SingleChoice (positional):
The portal computes this client-side before submission:
idx = index of selected option (0-based)
total = total number of options
score = max(1, (1 - idx/total) * MaxScore)
Example for q1.1 (4 options, MaxScore=15):
- Option 0: "Yes, fully applicable" →
(1 - 0/4) * 15 = 15 - Option 1: "Yes, partially applicable" →
(1 - 1/4) * 15 = 11.25 → 11 - Option 2: "Under evaluation" →
(1 - 2/4) * 15 = 7.5 → 7 - Option 3: "No" →
(1 - 3/4) * 15 = 3.75 → 3
The max(1, ...) floor ensures every answered question contributes at least 1 point.
Default Template: PQC Readiness Assessment
Template ID: pqc-readiness-v1
Version: 1
Created: 2025-01-01
Total questions: 18
Maximum possible score: 180
Section 1: Organizational & Regulatory Context (35 pts)
| ID | Question | Type | Options | Max Score |
|---|---|---|---|---|
| q1.1 | Is your organization subject to CNSA 2.0 / NSM-10 requirements? | SingleChoice | Yes fully / Yes partially / Evaluating / No | 15 |
| q1.2 | What is your organization's primary regulatory driver for PQC migration? | SingleChoice | CNSA 2.0 / NIST PQC / Best practice / Board directive / No driver | 10 |
| q1.3 | When must your organization demonstrate PQC compliance? | SingleChoice | 2027 / 2030 / 2033 / 2035 / No deadline | 10 |
Section 2: Estate & .NET/Azure Profile (20 pts)
| ID | Question | Type | Options | Max Score |
|---|---|---|---|---|
| q2.1 | How many .NET applications are in your cryptographic estate? | SingleChoice | 1-10 / 11-50 / 51-200 / 200-500 / 500+ / Unknown | 8 |
| q2.2 | What percentage uses Azure services (Key Vault, Managed HSM)? | SingleChoice | 0-25% / 26-50% / 51-75% / 76-100% / Unknown | 7 |
| q2.3 | What .NET versions are deployed in your estate? | MultiChoice | .NET FX 4.x / .NET 5/6 / .NET 7/8 / .NET 9 / .NET 10 | 5 |
Section 3: Cryptographic Inventory Maturity (35 pts)
| ID | Question | Type | Options | Max Score |
|---|---|---|---|---|
| q3.1 | Have you completed a full cryptographic asset inventory? | SingleChoice | Yes / Partially / In progress / Not started | 15 |
| q3.2 | Are quantum-vulnerable algorithms identified and classified? | SingleChoice | All identified / Most / Some / None | 10 |
| q3.3 | Do you have an automated cryptographic discovery tool in place? | SingleChoice | CI/CD scanning / Periodic manual / Evaluating CipherShift365 / No tool | 10 |
Section 4: Data Sensitivity & Longevity / HNDL (35 pts)
| ID | Question | Type | Options | Max Score |
|---|---|---|---|---|
| q4.1 | What is the longest required data confidentiality period? | SingleChoice | <1yr / 1-5yr / 5-10yr / 10-25yr / 25+yr / Unknown | 12 |
| q4.2 | Do you process data classified as HNDL sensitive? | SingleChoice | Yes — confirmed / Possibly / No / Unknown | 13 |
| q4.3 | What data categories require quantum-resistant protection? | MultiChoice | PII / Financial / IP / NSS/Classified / Healthcare / None | 10 |
Section 5: Migration Readiness (30 pts)
| ID | Question | Type | Options | Max Score |
|---|---|---|---|---|
| q5.1 | Do you have a PQC migration budget allocated? | SingleChoice | Fully funded / Partially / Budgeting / Not budgeted | 12 |
| q5.2 | What is your target migration completion timeline? | SingleChoice | 2025-26 / 2027-28 / 2029-30 / 2031-33 / After 2033 / No timeline | 10 |
| q5.3 | Have you identified PQC target algorithms for your estate? | SingleChoice | Mapped to assets / General targets / Evaluating / Not started | 8 |
Section 6: Governance & Timeline (25 pts)
| ID | Question | Type | Options | Max Score |
|---|---|---|---|---|
| q6.1 | Is there an executive sponsor for PQC migration? | SingleChoice | C-level / Director/VP / Discussion / No sponsor | 10 |
| q6.2 | How frequently is PQC readiness reported to leadership? | SingleChoice | Monthly / Quarterly / Annually / Ad-hoc | 8 |
| q6.3 | Do you maintain a PQC policy document aligned with CNSA 2.0? | SingleChoice | Published / Draft / Planned / No policy | 7 |
Readiness Bands
| Score % | Band | Interpretation |
|---|---|---|
| ≥ 85% | Advanced readiness | Organization is well-prepared for PQC migration. |
| 60–84% | Moderate readiness | Key gaps identified; prioritized plan recommended. |
| 30–59% | Early readiness | Significant gaps; comprehensive migration plan required. |
| < 30% | Critical | Immediate action required across multiple domains. |
Response Lifecycle
1. GET /v1/questionnaire/templates → Browse available assessments
2. GET /v1/questionnaire/templates/{id} → View full template with questions
3. Fill out answers (portal UI or programmatic)
4. POST /v1/questionnaire/submit → Submit response, receive score
5. GET /v1/questionnaire/responses → List prior responses
6. GET /v1/questionnaire/responses/{id} → View detailed response with score
Developer Manual
Architecture
Type Model
All types are defined in CipherShift365.Core.Runtime.Types.AssessmentTypes.cs:
public sealed record AssessmentTemplate(
string Id, // e.g., "pqc-readiness-v1"
string Title, // e.g., "PQC Readiness Assessment"
int Version, // e.g., 1
string Description,
IReadOnlyList<TemplateSection> Sections,
DateTimeOffset CreatedAt);
public sealed record TemplateSection(
string Id, // e.g., "s1"
string Title, // e.g., "Organizational & Regulatory Context"
string? Description,
IReadOnlyList<TemplateQuestion> Questions);
public sealed record TemplateQuestion(
string Id, // e.g., "q1.1"
string Text, // Full question text
QuestionType Type, // SingleChoice | MultiChoice | FreeText | Numeric
IReadOnlyList<string>? Options, // For SingleChoice/MultiChoice
int MaxScore, // Points achievable for this question
string? WeightCategory); // e.g., "Regulatory", "Estate", for grouping
public enum QuestionType { SingleChoice, MultiChoice, FreeText, Numeric }
public sealed record AssessmentResponse(
string Id, // Auto-generated (12-char hex)
string TemplateId,
int TemplateVersion, // Version of template when submitted
string Actor, // JWT Name claim
DateTimeOffset SubmittedAt,
IReadOnlyList<QuestionAnswer> Answers);
public sealed record QuestionAnswer(
string QuestionId, // Must match a TemplateQuestion.Id
string? Value, // Selected option text or free-text
int Score); // 1 to MaxScore
public sealed record AssessmentScore(
int TotalScore, // Sum of all answer scores
int MaxPossibleScore, // Sum of all question MaxScores
double ReadinessPercent, // TotalScore / MaxPossibleScore * 100
IReadOnlyDictionary<string, int> SectionScores, // Section ID → section total
string Summary, // Readiness band text
IReadOnlyList<string>? Recommendations); // Generated recommendations
Scoring Algorithm
The scoring algorithm in AssessmentService.Score():
public AssessmentScore Score(AssessmentResponse response)
{
var template = GetTemplate(response.TemplateId);
var questionMap = template.Sections
.SelectMany(s => s.Questions)
.ToDictionary(q => q.Id);
var sectionScores = new Dictionary<string, int>();
var recommendations = new List<string>();
int totalScore = 0, maxScore = 0;
foreach (var section in template.Sections)
{
int sectionScore = 0, sectionMax = 0;
foreach (var q in section.Questions)
{
var answer = response.Answers.FirstOrDefault(a => a.QuestionId == q.Id);
sectionScore += answer?.Score ?? 0; // Unanswered = 0
sectionMax += q.MaxScore;
maxScore += q.MaxScore;
}
sectionScores[section.Id] = sectionScore;
totalScore += sectionScore;
// Recommendation thresholds
var pct = sectionMax > 0 ? (double)sectionScore / sectionMax : 0;
if (pct < 0.4)
recommendations.Add($"{section.Title}: needs significant improvement ({pct:P0})");
else if (pct < 0.7)
recommendations.Add($"{section.Title}: moderate — further action recommended ({pct:P0})");
}
var readiness = maxScore > 0 ? (double)totalScore / maxScore * 100 : 0;
var summary = readiness switch
{
>= 85 => "Advanced readiness — organization is well-prepared for PQC migration.",
>= 60 => "Moderate readiness — key gaps identified; prioritized plan recommended.",
>= 30 => "Early readiness — significant gaps; comprehensive migration plan required.",
_ => "Critical — immediate action required across multiple domains.",
};
return new AssessmentScore(totalScore, maxScore, Math.Round(readiness, 1),
sectionScores.AsReadOnly(), summary,
recommendations.Count > 0 ? recommendations.AsReadOnly() : null);
}
Adding a Custom Template
var service = new AssessmentService();
// This would typically be called in Program.cs or a database seeder
var customTemplate = new AssessmentTemplate(
"financial-compliance-v1",
"Financial Services PQC Compliance",
1,
"Assesses PQC readiness for PCI DSS and SOX regulated environments.",
new[]
{
new TemplateSection("s1", "PCI DSS Cryptographic Requirements",
"Review of payment card data encryption.",
new[]
{
new TemplateQuestion("q1.1", "Is cardholder data encrypted at rest with quantum-resistant algorithms?",
QuestionType.SingleChoice,
new[] { "Yes — PQC algorithms used", "Yes — AES-256 only", "No" }, 10, "PCI"),
}.AsReadOnly()),
// ... more sections
}.AsReadOnly(),
DateTimeOffset.UtcNow);
// For the AssessmentService to pick it up, add to the internal _templates list.
// The current implementation only seeds the default template.
// To make templates extensible:
// 1. Add a public RegisterTemplate(AssessmentTemplate template) method to AssessmentService
// 2. Call it in Program.cs before any request
API Endpoints Reference
| Endpoint | Method | Roles | Request Body | Response |
|---|---|---|---|---|
/v1/questionnaire/templates |
GET | ConsultantAdmin, Auditor | — | [{id, title, version, description, sectionCount, questionCount}] |
/v1/questionnaire/templates/{id} |
GET | ConsultantAdmin, Auditor | — | Full template with sections, questions, options, maxScore |
/v1/questionnaire/submit |
POST | ConsultantAdmin, Auditor | {templateId, answers[{questionId, value, score}]} |
Response with full score object |
/v1/questionnaire/responses |
GET | ConsultantAdmin, Auditor | — | [{id, templateId, version, actor, submittedAt, readinessPercent, summary}] |
/v1/questionnaire/responses/{id} |
GET | ConsultantAdmin, Auditor | — | Response with answers array + full score breakdown |
Code Examples
Submit All 18 Questions (Complete Assessment)
var answers = new[]
{
// Section 1: Regulatory (35 pts max)
new QuestionAnswer("q1.1", "Yes, fully applicable", 15),
new QuestionAnswer("q1.2", "CNSA 2.0 compliance mandate", 10),
new QuestionAnswer("q1.3", "2030 (key establishment deadline)", 10),
// Section 2: Estate (20 pts max)
new QuestionAnswer("q2.1", "51-200", 5),
new QuestionAnswer("q2.2", "51-75%", 5),
new QuestionAnswer("q2.3", ".NET 6, .NET 7/8 deployed", 3),
// Section 3: Inventory (35 pts max)
new QuestionAnswer("q3.1", "Partially — some assets identified", 10),
new QuestionAnswer("q3.2", "Most identified", 7),
new QuestionAnswer("q3.3", "Evaluating/trialing CipherShift365", 5),
// Section 4: Data / HNDL (35 pts max)
new QuestionAnswer("q4.1", "10-25 years", 9),
new QuestionAnswer("q4.2", "Possibly — under evaluation", 8),
new QuestionAnswer("q4.3", "PII, Financial, IP data requires protection", 3),
// Section 5: Migration (30 pts max)
new QuestionAnswer("q5.1", "Partially funded", 9),
new QuestionAnswer("q5.2", "2029-2030", 7),
new QuestionAnswer("q5.3", "Under evaluation with CipherShift365", 5),
// Section 6: Governance (25 pts max)
new QuestionAnswer("q6.1", "Yes — Director/VP level", 7),
new QuestionAnswer("q6.2", "Quarterly", 6),
new QuestionAnswer("q6.3", "Draft in progress", 5),
}.AsReadOnly();
var response = service.Submit("pqc-readiness-v1", "Alice", answers);
var score = service.Score(response);
// TotalScore: ~142, MaxScore: 180
// ReadinessPercent: ~78.9% → "Moderate readiness"
// Recommendations: ["Estate Profile: moderate — further action recommended (55%)", ...]
PowerShell — Query Responses via API
$token = (Invoke-RestMethod -Uri "http://localhost:5000/v1/auth/token" -Method Post -Body '{"clientId":"admin-001","secret":"any"}' -ContentType "application/json").token
headers = @{ Authorization = "Bearertoken" }
# Get all responses
headers
foreach (responses) {
Write-Host "r.actor): r.readinessPercent)% — r.summary)"
}
# Get detailed response
(headers
foreach (detail.answers) {
Write-Host " a.questionId): a.value) (a.score) pts)"
}
Programmatically Compare Two Responses
var resp1 = service.GetResponse("abc123");
var resp2 = service.GetResponse("def456");
var score1 = service.Score(resp1!);
var score2 = service.Score(resp2!);
Console.WriteLine($"Response 1 ({resp1!.SubmittedAt:yyyy-MM-dd}): {score1.ReadinessPercent:F1}%");
Console.WriteLine($"Response 2 ({resp2!.SubmittedAt:yyyy-MM-dd}): {score2.ReadinessPercent:F1}%");
Console.WriteLine($"Delta: {score2.ReadinessPercent - score1.ReadinessPercent:+#.#;-#.#}%");
// Section-level comparison
foreach (var (sectionId, sectionScore1) in score1.SectionScores)
{
var sectionScore2 = score2.SectionScores[sectionId];
var delta = sectionScore2 - sectionScore1;
Console.WriteLine($" {sectionId}: {sectionScore1} → {sectionScore2} ({delta:+#;-#})");
}
Configuration Options
| Setting | Location | Default | Description |
|---|---|---|---|
| Default template | DefaultAssessmentTemplate.Create() |
pqc-readiness-v1 |
The only template seeded at startup |
| Recommendation threshold — critical | AssessmentService.Score() |
< 40% of section max | Generates "needs significant improvement" |
| Recommendation threshold — moderate | AssessmentService.Score() |
40–69% of section max | Generates "moderate — further action" |
| Advanced readiness band | AssessmentService.Score() |
≥ 85% | "Advanced readiness" summary |
| Moderate readiness band | AssessmentService.Score() |
60–84% | "Moderate readiness" summary |
| Early readiness band | AssessmentService.Score() |
30–59% | "Early readiness" summary |
| Critical band | AssessmentService.Score() |
< 30% | "Critical" summary |
| Minimum score per answer | Portal SelectOption() |
max(1, computed) |
Floor to ensure answered questions contribute |
| FreeText minimal score | Portal SetText() |
1 (≤10 chars) or 3 (>10 chars) | Heuristic for unvalidated text |
Design Decisions & Rationale
| Decision | Rationale |
|---|---|
| Records over classes | All types are C# record types — value equality, immutability, with-expressions for templates. |
| Score stored with answer | The QuestionAnswer record carries its own Score. This means scores are determined at submission time and the response is self-contained. The Score() method recalculates independently for verification but uses the stored scores as primary. |
| Positional scoring for SingleChoice | Simple, transparent, no hidden weights. The assumption is that options are ordered from best to worst. Organizations can adjust by reordering options in their custom template. |
| In-memory storage | Templates and responses are held in List<T>. No database, no file I/O. Suitable for engagement-tier use. Production deployments should replace with persistent storage. |
| Section-level recommendations | Threshold-based rather than absolute. Avoids overwhelming the user — only sections with clear gaps get recommendations. |
| Version-stamped responses | Each AssessmentResponse records TemplateVersion. If a template is updated, older responses remain interpretable against their original version. |