Definition
Comprehensive verification is the disciplined, multi-technique approach to validating that software systems behave correctly, safely, and reliably across all intended operational conditions. It combines complementary verification methods -- static analysis, dynamic testing, type checking, property-based testing, formal proofs, and runtime monitoring -- into a layered assurance strategy where each technique catches classes of defects that others miss.
The key principle underlying comprehensive verification is that no single verification technique is sufficient. Unit tests verify specific scenarios but cannot cover all possible inputs. Static analysis catches structural defects but cannot reason about runtime behavior. Type systems prevent category errors but cannot express all correctness properties. Comprehensive verification acknowledges these limitations and combines techniques to achieve defense-in-depth for software correctness.
Overview
Software verification has evolved from simple manual testing to a sophisticated discipline incorporating insights from formal methods, type theory, abstract interpretation, and empirical software engineering. The evolution reflects a growing understanding that software defects are diverse in nature and require diverse detection approaches.
The Verification Spectrum
Verification techniques span a spectrum from lightweight and fast to heavyweight and thorough:
Lightweight (fast, approximate):
- Compiler warnings
- Linting (Credo)
- Code formatting checks
- Basic type checking
Medium-weight (moderate cost, high coverage):
- Unit testing
- Integration testing
- Static analysis (Dialyzer)
- Code coverage measurement
Heavyweight (high cost, strong guarantees):
- Property-based testing (StreamData)
- Mutation testing
- Formal specification
- Theorem proving (Lean4)
Comprehensive verification employs techniques from across this spectrum, using lightweight techniques as fast feedback loops and heavyweight techniques for critical invariants.
The Verification Gap
The "verification gap" is the space between what a system is specified to do and what has been verified. In most software projects, this gap is large -- specifications are informal, test coverage is incomplete, and runtime behavior is only partially monitored. Comprehensive verification aims to minimize this gap through systematic application of complementary techniques.
The Prismatic Platform addresses the verification gap through the Trinity Gate system, which requires claims to pass structural consistency, logical consistency, and formal necessity checks before being accepted. This is the most rigorous verification framework in the platform's quality system.
Technical Details
Static Analysis Layer
Static analysis examines code without executing it, detecting structural defects, type inconsistencies, and coding standard violations:
defmodule Prismatic.Verification.StaticAnalyzer do
@moduledoc """
Orchestrates multi-tool static analysis across the codebase.
Integrates compiler warnings, Dialyzer, and Credo into a
unified verification pipeline.
"""
@type analysis_result :: %{
compiler_warnings: [warning()],
dialyzer_findings: [finding()],
credo_issues: [issue()],
overall_status: :pass | :warn | :fail
}
@type warning :: %{
file: String.t(),
line: pos_integer(),
message: String.t(),
severity: :warning | :error
}
@type finding :: %{
file: String.t(),
line: pos_integer(),
type: atom(),
message: String.t()
}
@type issue :: %{
file: String.t(),
line: pos_integer(),
check: module(),
message: String.t(),
priority: :higher | :high | :normal | :low
}
@spec run_full_analysis(keyword()) :: {:ok, analysis_result()} | {:error, term()}
def run_full_analysis(opts \\ []) do
results = %{
compiler_warnings: run_compiler_check(opts),
dialyzer_findings: run_dialyzer(opts),
credo_issues: run_credo(opts),
overall_status: :pass
}
overall =
cond do
has_errors?(results) -> :fail
has_warnings?(results) -> :warn
true -> :pass
end
{:ok, %{results | overall_status: overall}}
end
defp run_compiler_check(_opts) do
# Invokes mix compile --warnings-as-errors --force
# Returns list of warnings/errors
[]
end
defp run_dialyzer(_opts) do
# Invokes mix dialyzer
# Returns list of type specification violations
[]
end
defp run_credo(_opts) do
# Invokes mix credo --strict
# Returns list of code quality issues
[]
end
defp has_errors?(results) do
Enum.any?(results.compiler_warnings, &(&1.severity == :error)) or
not Enum.empty?(results.dialyzer_findings)
end
defp has_warnings?(results) do
not Enum.empty?(results.compiler_warnings) or
not Enum.empty?(results.credo_issues)
end
endType Verification with Dialyzer
Dialyzer (Discrepancy Analyzer) performs success typing analysis on Elixir code, detecting type errors, unreachable code, and specification violations without requiring type annotations (though @spec annotations improve its precision):
defmodule Prismatic.Verification.TypeVerification do
@moduledoc """
Demonstrates comprehensive type verification using Elixir's
@spec annotations and Dialyzer success typing analysis.
"""
@type verification_result :: :verified | {:violations, [violation()]}
@type violation :: %{
module: module(),
function: atom(),
arity: non_neg_integer(),
expected_type: String.t(),
actual_type: String.t()
}
@spec verify_module_specs(module()) :: verification_result()
def verify_module_specs(module) do
case Code.Typespec.fetch_specs(module) do
{:ok, specs} ->
violations =
specs
|> Enum.flat_map(fn {{fun, arity}, type_specs} ->
verify_function_spec(module, fun, arity, type_specs)
end)
case violations do
[] -> :verified
found -> {:violations, found}
end
:error ->
{:violations, [%{
module: module,
function: :__module__,
arity: 0,
expected_type: "specs defined",
actual_type: "no specs found"
}]}
end
end
@spec coverage_report(module()) :: %{
functions_with_specs: non_neg_integer(),
functions_without_specs: non_neg_integer(),
coverage_percentage: float()
}
def coverage_report(module) do
all_functions = module.__info__(:functions)
specs = case Code.Typespec.fetch_specs(module) do
{:ok, s} -> s
:error -> []
end
spec_funs = MapSet.new(specs, fn {{f, a}, _} -> {f, a} end)
with_specs = Enum.count(all_functions, &MapSet.member?(spec_funs, &1))
total = length(all_functions)
%{
functions_with_specs: with_specs,
functions_without_specs: total - with_specs,
coverage_percentage: if(total > 0, do: with_specs / total * 100.0, else: 0.0)
}
end
defp verify_function_spec(_module, _fun, _arity, _type_specs), do: []
endProperty-Based Testing
Property-based testing verifies that system properties hold across randomly generated inputs, catching edge cases that example-based tests miss:
defmodule Prismatic.Verification.PropertyTest do
@moduledoc """
Demonstrates property-based testing patterns used in the
Prismatic Platform for comprehensive verification of
algorithmic correctness.
"""
use ExUnit.Case
use ExUnitProperties
# Property: encoding then decoding is identity
property "JSON round-trip preserves data" do
check all data <- term_generator() do
encoded = Jason.encode!(data)
decoded = Jason.decode!(encoded)
assert structurally_equivalent?(data, decoded)
end
end
# Property: sorting is idempotent
property "sorting twice produces the same result as sorting once" do
check all list <- list_of(integer()) do
once = Enum.sort(list)
twice = Enum.sort(once)
assert once == twice
end
end
# Property: risk scores are bounded
property "risk scores are always between 0.0 and 1.0" do
check all likelihood <- member_of([:low, :medium, :high, :critical]),
impact <- member_of([:low, :medium, :high, :critical]) do
score = calculate_risk(likelihood, impact)
assert score >= 0.0 and score <= 1.0
end
end
# Property: supervision tree restart counts are monotonic
property "restart counter never decreases within a window" do
check all restarts <- list_of(positive_integer(), min_length: 2, max_length: 100) do
running_counts = Enum.scan(restarts, 0, &(&1 + &2))
pairs = Enum.zip(running_counts, tl(running_counts))
assert Enum.all?(pairs, fn {a, b} -> b >= a end)
end
end
defp term_generator do
one_of([
integer(),
float(),
string(:printable),
boolean(),
constant(nil),
list_of(integer(), max_length: 5),
map_of(string(:alphanumeric, min_length: 1), integer(), max_length: 5)
])
end
defp structurally_equivalent?(original, decoded) when is_map(original) and is_map(decoded) do
Map.keys(original)
|> Enum.all?(fn key ->
string_key = to_string(key)
Map.has_key?(decoded, string_key) and
structurally_equivalent?(Map.get(original, key), Map.get(decoded, string_key))
end)
end
defp structurally_equivalent?(a, b), do: a == b
defp calculate_risk(likelihood, impact) do
l = case likelihood do
:low -> 0.25
:medium -> 0.5
:high -> 0.75
:critical -> 1.0
end
i = case impact do
:low -> 0.25
:medium -> 0.5
:high -> 0.75
:critical -> 1.0
end
l * i
end
endTrinity Gate Verification
The Trinity Gate is the Prismatic Platform's highest verification standard, requiring claims to pass three independent checks:
defmodule Prismatic.Verification.TrinityGate do
@moduledoc """
The Trinity Gate verification system requires all claims
to pass three independent consistency checks before
being accepted as verified.
"""
@type claim :: %{
id: String.t(),
statement: String.t(),
evidence: [evidence()],
formal_proof: formal_proof() | nil
}
@type evidence :: %{
source: String.t(),
type: :test | :analysis | :proof | :observation,
confidence: float()
}
@type formal_proof :: %{
system: :lean4 | :coq | :agda,
theorem: String.t(),
proof_status: :verified | :pending | :failed
}
@type gate_result :: %{
structural: :pass | :fail,
logical: :pass | :fail,
formal: :pass | :fail,
overall: :pass | :fail,
findings: [String.t()]
}
@spec verify(claim()) :: gate_result()
def verify(claim) do
structural = verify_structural_consistency(claim)
logical = verify_logical_consistency(claim)
formal = verify_formal_necessity(claim)
overall =
if structural == :pass and logical == :pass and formal == :pass do
:pass
else
:fail
end
%{
structural: structural,
logical: logical,
formal: formal,
overall: overall,
findings: collect_findings(claim, structural, logical, formal)
}
end
@doc """
Gate 1: Structural Consistency
Verifies that the claim's evidence forms a valid directed
acyclic graph with no circular dependencies.
"""
@spec verify_structural_consistency(claim()) :: :pass | :fail
def verify_structural_consistency(claim) do
if length(claim.evidence) >= 2 and no_circular_deps?(claim.evidence) do
:pass
else
:fail
end
end
@doc """
Gate 2: Logical Consistency
Verifies that the claim follows from its evidence through
valid logical inference rules.
"""
@spec verify_logical_consistency(claim()) :: :pass | :fail
def verify_logical_consistency(claim) do
min_confidence =
claim.evidence
|> Enum.map(& &1.confidence)
|> Enum.min(fn -> 0.0 end)
if min_confidence >= 0.8 and evidence_supports_claim?(claim) do
:pass
else
:fail
end
end
@doc """
Gate 3: Formal Necessity
For critical claims, verifies through formal proof systems
that the claim necessarily holds.
"""
@spec verify_formal_necessity(claim()) :: :pass | :fail
def verify_formal_necessity(%{formal_proof: nil}), do: :fail
def verify_formal_necessity(%{formal_proof: %{proof_status: :verified}}), do: :pass
def verify_formal_necessity(_), do: :fail
defp no_circular_deps?(_evidence), do: true
defp evidence_supports_claim?(_claim), do: true
defp collect_findings(claim, structural, logical, formal) do
findings = []
findings =
if structural == :fail do
["Structural: Claim #{.} lacks sufficient independent evidence" | findings]
else
findings
end
findings =
if logical == :fail do
["Logical: Evidence does not logically support claim #{.}" | findings]
else
findings
end
findings =
if formal == :fail do
["Formal: No verified formal proof for claim #{.}" | findings]
else
findings
end
findings
end
endQuality Gates Pipeline
The quality gates pipeline integrates all verification techniques into a single pass/fail decision:
defmodule Prismatic.Verification.QualityGatesPipeline do
@moduledoc """
Orchestrates the full verification pipeline, running all
quality gates in sequence and producing a unified result.
"""
@type gate :: %{
name: String.t(),
check: (() -> :pass | {:fail, String.t()}),
blocking: boolean(),
timeout_ms: pos_integer()
}
@type pipeline_result :: %{
status: :pass | :fail,
gates_passed: non_neg_integer(),
gates_failed: non_neg_integer(),
gate_results: [%{name: String.t(), status: :pass | :fail, duration_ms: non_neg_integer()}],
blocking_failures: [String.t()]
}
@spec run_pipeline([gate()]) :: pipeline_result()
def run_pipeline(gates) do
results =
Enum.map(gates, fn gate ->
start_time = System.monotonic_time(:millisecond)
status = run_gate(gate)
duration = System.monotonic_time(:millisecond) - start_time
%{name: gate.name, status: status, duration_ms: duration, blocking: gate.blocking}
end)
blocking_failures =
results
|> Enum.filter(fn r -> r.blocking and r.status == :fail end)
|> Enum.map(& &1.name)
%{
status: if(Enum.empty?(blocking_failures), do: :pass, else: :fail),
gates_passed: Enum.count(results, &(&1.status == :pass)),
gates_failed: Enum.count(results, &(&1.status == :fail)),
gate_results: results,
blocking_failures: blocking_failures
}
end
defp run_gate(gate) do
task = Task.async(fn -> gate.check.() end)
case Task.yield(task, gate.timeout_ms) || Task.shutdown(task) do
{:ok, :pass} -> :pass
{:ok, {:fail, _reason}} -> :fail
nil -> :fail
end
end
endImplementation in Prismatic Platform
11-Phase Pre-Commit Verification
The Prismatic Platform enforces comprehensive verification through an 11-phase pre-commit hook that runs automatically before every commit:
- Compilation (zero warnings,
--warnings-as-errors) - Formatting (
mix format --check-formatted) - Credo (
mix credo --strict) - Dialyzer (success typing analysis)
- Tests (
mix testwith coverage) - Quality gates (
mix quality.gates) - Forbidden patterns (no mocks, stubs, placeholders in production code)
- Template validation (promo site templates)
- Security checks (no secrets, no hardcoded credentials)
- Design consistency (TailwindCSS, Flowbite patterns)
- Quality debt (QDP quota compliance)
Quality Score: 100/100
The platform maintains a perfect quality score across 13 verification domains: Dialyzer, Credo, Compilation, DateTime Precision, Guard Functions, @impl Coverage, Memory Safety, Performance, Regression Prevention, Timing Patterns, TODO Management, Typespec Coverage, and Unsafe Map Access. All domains report zero violations.
Trinity Gate Integration
Critical claims about platform behavior -- security properties, performance guarantees, correctness invariants -- must pass the Trinity Gate's three checks: structural consistency (evidence DAG validation), logical consistency (inference rule compliance), and formal necessity (Lean4 proof verification where applicable).
Continuous Verification in CI/CD
The GitLab CI pipeline runs the full verification suite on every push, blocking merges that introduce any verification failure. The pipeline includes compilation, testing, static analysis, and deployment checks.
Comparison with Alternatives
| Approach | Coverage | Cost | Confidence | Prismatic Position |
|---|---|---|---|---|
| Manual testing only | Low, ad-hoc | High per-test, low coverage | Low | Insufficient alone |
| Unit tests only | Example-specific | Medium | Medium | Foundation layer |
| Static analysis only | Structural defects | Low (automated) | Medium-high for type errors | Essential layer |
| Property-based testing | Input-space coverage | Medium-high | High for tested properties | Advanced layer |
| Formal verification | Mathematical certainty | Very high | Highest | Applied to critical invariants |
| Comprehensive verification | Multi-dimensional | High (amortized) | Very high | Full-stack approach |
Best Practices
-
Layer techniques by cost and coverage: Use cheap techniques (compiler warnings, linting) everywhere, medium-cost techniques (unit tests, static analysis) broadly, and expensive techniques (property-based testing, formal proofs) on critical paths.
-
Automate everything: Every verification technique should run automatically as part of the development workflow. Manual verification steps are skipped under pressure.
-
Make failures informative: Verification failures should provide clear, actionable feedback about what is wrong, where it is wrong, and how to fix it.
-
Maintain verification speed: Developers bypass slow verification. Keep the fast feedback loop under 30 seconds for local development and under 10 minutes for CI.
-
Test the tests: Use mutation testing to verify that tests actually catch defects. A test suite with 100% code coverage can still miss bugs if the assertions are too weak.
-
Version verification configuration: Treat linting rules, type specifications, and test configurations as code that is reviewed, versioned, and evolved alongside the system.
-
Track verification metrics: Monitor test count, coverage percentage, static analysis finding count, and verification runtime over time. Trends reveal systemic issues.
-
Never skip verification: The NO MERCY doctrine prohibits bypassing verification gates. Every commit must pass all verification phases without exception.
Common Pitfalls
-
Coverage worship: Optimizing for code coverage percentage rather than defect detection effectiveness. 100% line coverage with weak assertions provides false confidence.
-
Flaky tests: Tests that pass or fail non-deterministically undermine trust in the verification system. Every flaky test must be fixed immediately or removed.
-
Verification fatigue: Too many low-value checks (style nits, trivial warnings) create noise that causes developers to ignore all verification output, including critical findings.
-
Missing integration tests: Thorough unit tests for individual components do not verify that components work correctly together. Integration tests are essential.
-
Stale specifications: Type specifications and documentation that do not match the actual implementation create false confidence and mask real issues.
-
Ignoring Dialyzer warnings: Dialyzer warnings are frequently dismissed as false positives. In practice, most Dialyzer warnings indicate real issues with type specifications or unreachable code.
-
Testing implementation instead of behavior: Tests that verify internal implementation details are brittle and must be rewritten with every refactoring. Test observable behavior instead.
Use Cases
- Safety-critical systems where formal verification of critical invariants is required by regulation or risk assessment.
- Financial platforms where arithmetic correctness, transaction integrity, and audit completeness must be verified continuously.
- Security-sensitive applications where input validation, authentication logic, and authorization rules must be verified through multiple independent techniques.
- Platform evolution where comprehensive verification ensures that improvements and refactoring do not introduce regressions.
- Compliance-driven development where regulatory frameworks (NIS2, SOC2, GDPR) require evidence of systematic quality assurance practices.
- Open source projects where comprehensive verification builds confidence in code quality for external contributors and users.
Related Concepts
Comprehensive verification connects to many quality and correctness concepts in the Prismatic Platform:
- Quality Gates -- the enforcement mechanism that blocks non-verified code from entering the codebase
- Static Analysis -- code examination without execution that catches structural defects
- Property-Based Testing -- verification of system properties across randomly generated inputs
- Dialyzer -- Erlang/Elixir success typing analyzer for type verification
- Credo -- Elixir static code analysis tool for consistency and readability
- Trinity Gate -- three-gate verification requiring structural, logical, and formal consistency
- Theorem Proving -- formal mathematical proof of system properties
- Test Coverage -- measurement of how much code is exercised by the test suite
- Regression Testing -- verification that changes do not break previously working functionality
- Quality Assurance -- the systematic process of ensuring quality standards are met
See Also
- Glossary Index -- complete listing of all platform terminology
- Quality DNA -- cross-session quality state tracking and continuity
- Quality Floor Guardian -- autonomous quality monitoring and enforcement
- Code Coverage -- quantitative measurement of test suite thoroughness
Built with precision. Ready for the future.
Created by Tomas Korcak (korczis) | Open Source under GHL