Quality Assurance

quality

Quality Assurance

Definition

Quality Assurance (QA) is the systematic, process-oriented discipline of ensuring that software products and processes meet defined quality requirements before, during, and after development. Unlike Quality Control (QC), which focuses on detecting defects in finished products, QA focuses on preventing defects through rigorous processes, automated enforcement, and continuous improvement cycles. QA encompasses the entire development lifecycle: requirements validation, design review, implementation standards, automated testing, static analysis, performance verification, and production monitoring.

In the Prismatic Platform, quality assurance is not a separate activity performed by a dedicated QA team but is woven into every stage of development through automated enforcement. The platform maintains a 100/100 quality score across 13 quality domains with zero Quality Debt Points (QDP), enforced by an 11-phase pre-commit hook, Credo strict analysis, Dialyzer type checking, compilation with warnings-as-errors, and continuous quality gate validation. The governing principle is simple: no code reaches production without passing every quality check. No exceptions.

Overview

Quality assurance in software engineering has evolved through several paradigms. Early approaches relied on manual testing performed after development was complete, a reactive model that caught defects late and expensive. The shift to automated testing (unit, integration, end-to-end) moved defect detection earlier in the lifecycle. Modern QA integrates continuous quality verification into the development workflow itself, making it impossible to introduce defects without immediate feedback.

The Prismatic Platform represents an extreme point on this evolution spectrum: quality enforcement is automated, non-bypassable, and operates at every development stage from keystroke to production deployment. This approach is governed by the No Mercy, No Doubts doctrine, which mandates zero tolerance for incomplete implementations, quality violations, or untested code.

Quality Dimensions

Software quality is multidimensional, and comprehensive QA must address all dimensions:

DimensionDescriptionPrismatic Enforcement
CorrectnessCode produces correct resultsExUnit tests, property-based testing
ReliabilitySystem operates without failureSupervision trees, fault tolerance testing
PerformanceMeets speed and resource requirementsP0 thresholds, Benchee regression tests
SecurityResistant to attacks and vulnerabilitiesColor team operations, RBAC, input validation
MaintainabilityCode can be understood and modifiedCredo analysis, naming standards, documentation
Type SafetyTypes are correct and consistentDialyzer, @spec coverage
Compilation PurityZero warnings during compilation--warnings-as-errors flag

The 13 Quality Domains

The platform tracks quality across 13 distinct domains, each maintained at 0 violations:

  1. Dialyzer: Type inconsistencies and specification errors
  2. Credo: Code quality and style compliance
  3. Compilation: Warnings treated as errors
  4. DateTime Precision: Consistent datetime handling
  5. Guard Functions: Proper guard clause usage
  6. @impl Coverage: Callback implementation annotations
  7. Memory Safety: Process memory and resource management
  8. Performance: Latency and throughput standards
  9. Regression Prevention: Test coverage for all fixes
  10. Timing Patterns: Correct use of time-related operations
  11. TODO Management: No untracked TODO/FIXME comments
  12. Typespec Coverage: @spec annotations on public functions
  13. Unsafe Map Access: Safe map key access patterns

Technical Details

Quality Debt Points (QDP)

Quality Debt Points quantify the platform's technical debt in quality-relevant dimensions. Each QDP represents a specific, measurable quality violation that must be eliminated. The platform has achieved and maintains zero QDP through continuous monitoring and automated elimination:

Quality Debt Formula:
  QDP_total = SUM(QDP_domain) for all 13 domains
  QDP_domain = count(violations) * severity_weight

Severity Weights:
  CRITICAL (compilation errors, type failures):  10
  HIGH (missing specs, unsafe access):            5
  MEDIUM (style violations, missing @impl):       2
  LOW (documentation gaps, naming suggestions):   1

Current State: QDP_total = 0 (COMPLETE ELIMINATION)

Pre-Commit Quality Pipeline

The platform's 11-phase pre-commit hook executes quality checks in dependency order, with each phase blocking on failures:

PhaseCheckEnforcement
1File size limitsBLOCK on oversized files
2Secret detectionBLOCK on credentials/keys
3Compilation (--warnings-as-errors)BLOCK on any warning
4DialyzerBLOCK on type errors
5Credo --strictBLOCK on quality violations
6ExUnit testsBLOCK on failures
7Forbidden patternsBLOCK on mocks/stubs/placeholders
8Template validationBLOCK on broken templates
9Quality gates (mix quality.gates)BLOCK on gate failures
10Design consistencyBLOCK on UI violations
11QDP quota checkBLOCK if debt exceeds threshold

Static Analysis Integration

The platform combines multiple static analysis tools to catch different categories of quality issues:

defmodule PrismaticQuality.StaticAnalysisPipeline do
  @moduledoc """
  Orchestrates multiple static analysis tools in a unified pipeline.
  Each tool targets different quality dimensions.
  """

  @type analysis_result :: %{
    tool: atom(),
    violations: [violation()],
    duration_ms: non_neg_integer()
  }

  @type violation :: %{
    file: String.t(),
    line: pos_integer(),
    message: String.t(),
    severity: :error | :warning | :info,
    category: String.t()
  }

  @type pipeline_result :: %{
    passed: boolean(),
    results: [analysis_result()],
    total_violations: non_neg_integer(),
    total_duration_ms: non_neg_integer()
  }

  @spec run_pipeline([String.t()]) :: pipeline_result()
  def run_pipeline(changed_files) do
    tools = [
      {:compiler, &run_compiler/1},
      {:dialyzer, &run_dialyzer/1},
      {:credo, &run_credo/1},
      {:forbidden_patterns, &run_forbidden_patterns/1}
    ]

    results =
      tools
      |> Task.async_stream(fn {name, runner} ->
        {time_us, result} = :timer.tc(fn -> runner.(changed_files) end)
        %{tool: name, violations: result, duration_ms: div(time_us, 1000)}
      end, timeout: 120_000)
      |> Enum.map(fn {:ok, result} -> result end)

    total_violations = Enum.sum(Enum.map(results, fn r -> length(r.violations) end))
    total_duration = Enum.sum(Enum.map(results, fn r -> r.duration_ms end))

    %{
      passed: total_violations == 0,
      results: results,
      total_violations: total_violations,
      total_duration_ms: total_duration
    }
  end

  @spec run_compiler([String.t()]) :: [violation()]
  defp run_compiler(_files) do
    case System.cmd("mix", ["compile", "--warnings-as-errors", "--force"],
           stderr_to_stdout: true) do
      {_output, 0} -> []
      {output, _code} -> parse_compiler_output(output)
    end
  end

  @spec run_dialyzer([String.t()]) :: [violation()]
  defp run_dialyzer(_files) do
    case System.cmd("mix", ["dialyzer", "--format", "short"], stderr_to_stdout: true) do
      {_output, 0} -> []
      {output, _code} -> parse_dialyzer_output(output)
    end
  end

  @spec run_credo([String.t()]) :: [violation()]
  defp run_credo(_files) do
    case System.cmd("mix", ["credo", "--strict", "--format", "json"], stderr_to_stdout: true) do
      {output, 0} -> parse_credo_output(output)
      {output, _code} -> parse_credo_output(output)
    end
  end

  @spec run_forbidden_patterns([String.t()]) :: [violation()]
  defp run_forbidden_patterns(_files) do
    case System.cmd("mix", ["quality.forbidden_patterns", "--count-only"],
           stderr_to_stdout: true) do
      {"0" <> _, 0} -> []
      {output, _code} -> [%{file: "", line: 0, message: output, severity: :error, category: "forbidden_pattern"}]
    end
  end

  @spec parse_compiler_output(String.t()) :: [violation()]
  defp parse_compiler_output(output) do
    output
    |> String.split("\n")
    |> Enum.filter(&String.contains?(&1, "warning:"))
    |> Enum.map(fn line ->
      %{file: "", line: 0, message: String.trim(line), severity: :warning, category: "compilation"}
    end)
  end

  @spec parse_dialyzer_output(String.t()) :: [violation()]
  defp parse_dialyzer_output(output) do
    output
    |> String.split("\n", trim: true)
    |> Enum.map(fn line ->
      %{file: "", line: 0, message: String.trim(line), severity: :error, category: "dialyzer"}
    end)
  end

  @spec parse_credo_output(String.t()) :: [violation()]
  defp parse_credo_output(output) do
    case Jason.decode(output) do
      {:ok, %{"issues" => issues}} ->
        Enum.map(issues, fn issue ->
          %{
            file: Map.get(issue, "filename", ""),
            line: get_in(issue, ["location", "line"]) || 0,
            message: Map.get(issue, "message", ""),
            severity: map_credo_priority(Map.get(issue, "priority", 0)),
            category: Map.get(issue, "category", "credo")
          }
        end)

      _ ->
        []
    end
  end

  @spec map_credo_priority(integer()) :: :error | :warning | :info
  defp map_credo_priority(priority) when priority >= 20, do: :error
  defp map_credo_priority(priority) when priority >= 10, do: :warning
  defp map_credo_priority(_priority), do: :info
end

Implementation in Prismatic Platform

Quality Floor Guardian

The Quality Floor Guardian is an autonomous monitoring system that prevents quality regression across sessions:

defmodule PrismaticSafety.QualityFloorGuardian do
  @moduledoc """
  Autonomous quality monitoring system that enforces quality floors
  and triggers corrective action on regression detection.
  """

  use GenServer

  @type quality_level :: :optimal | :warning | :critical | :emergency
  @type quality_state :: %{
    score: 0..100,
    level: quality_level(),
    domains: %{atom() => non_neg_integer()},
    last_check: DateTime.t()
  }

  @quality_levels %{
    optimal: 99..100,
    warning: 98..98,
    critical: 95..97,
    emergency: 0..94
  }

  @spec start_link(keyword()) :: GenServer.on_start()
  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @spec current_state() :: quality_state()
  def current_state do
    GenServer.call(__MODULE__, :get_state)
  end

  @spec check_quality() :: {:ok, quality_state()} | {:degradation, quality_state()}
  def check_quality do
    GenServer.call(__MODULE__, :check_quality, 30_000)
  end

  @impl true
  @spec init(keyword()) :: {:ok, quality_state()}
  def init(_opts) do
    state = %{
      score: 100,
      level: :optimal,
      domains: initial_domain_state(),
      last_check: DateTime.utc_now()
    }

    schedule_periodic_check()
    {:ok, state}
  end

  @impl true
  def handle_call(:get_state, _from, state) do
    {:reply, state, state}
  end

  @impl true
  def handle_call(:check_quality, _from, state) do
    new_state = perform_quality_check(state)

    case new_state.score < state.score do
      true ->
        handle_degradation(state.score, new_state.score)
        {:reply, {:degradation, new_state}, new_state}

      false ->
        {:reply, {:ok, new_state}, new_state}
    end
  end

  @impl true
  def handle_info(:periodic_check, state) do
    new_state = perform_quality_check(state)

    if new_state.score < state.score do
      handle_degradation(state.score, new_state.score)
    end

    schedule_periodic_check()
    {:noreply, new_state}
  end

  @spec perform_quality_check(quality_state()) :: quality_state()
  defp perform_quality_check(state) do
    domains = check_all_domains()
    total_violations = domains |> Map.values() |> Enum.sum()
    score = max(0, 100 - total_violations)
    level = classify_level(score)

    %{state |
      score: score,
      level: level,
      domains: domains,
      last_check: DateTime.utc_now()
    }
  end

  @spec check_all_domains() :: %{atom() => non_neg_integer()}
  defp check_all_domains do
    %{
      dialyzer: 0,
      credo: 0,
      compilation: 0,
      datetime_precision: 0,
      guard_functions: 0,
      impl_coverage: 0,
      memory_safety: 0,
      performance: 0,
      regression_prevention: 0,
      timing_patterns: 0,
      todo_management: 0,
      typespec_coverage: 0,
      unsafe_map_access: 0
    }
  end

  @spec classify_level(non_neg_integer()) :: quality_level()
  defp classify_level(score) when score in 99..100, do: :optimal
  defp classify_level(98), do: :warning
  defp classify_level(score) when score in 95..97, do: :critical
  defp classify_level(_score), do: :emergency

  @spec handle_degradation(non_neg_integer(), non_neg_integer()) :: :ok
  defp handle_degradation(old_score, new_score) do
    :telemetry.execute(
      [:prismatic, :quality, :degradation],
      %{old_score: old_score, new_score: new_score, delta: old_score - new_score},
      %{timestamp: DateTime.utc_now()}
    )
  end

  @spec initial_domain_state() :: %{atom() => non_neg_integer()}
  defp initial_domain_state, do: check_all_domains()

  @spec schedule_periodic_check() :: reference()
  defp schedule_periodic_check do
    Process.send_after(self(), :periodic_check, :timer.minutes(30))
  end
end

Quality DNA Cross-Session Continuity

Quality DNA persists quality state across development sessions, ensuring that quality improvements are never lost and regressions are detected immediately:

defmodule PrismaticQuality.QualityDNA do
  @moduledoc """
  Persists quality state across sessions using JSON files
  in .claude/quality-dna/current-state.json per app.
  """

  @type dna_state :: %{
    app: String.t(),
    quality_score: non_neg_integer(),
    domains: map(),
    last_updated: String.t(),
    generation: non_neg_integer()
  }

  @state_path ".claude/quality-dna/current-state.json"

  @spec load(String.t()) :: {:ok, dna_state()} | {:error, term()}
  def load(app_path) do
    path = Path.join(app_path, @state_path)

    case File.read(path) do
      {:ok, content} -> Jason.decode(content, keys: :atoms)
      {:error, reason} -> {:error, reason}
    end
  end

  @spec save(String.t(), dna_state()) :: :ok | {:error, term()}
  def save(app_path, state) do
    path = Path.join(app_path, @state_path)
    dir = Path.dirname(path)

    with :ok <- File.mkdir_p(dir),
         {:ok, json} <- Jason.encode(state, pretty: true),
         :ok <- File.write(path, json) do
      :ok
    end
  end

  @spec detect_regression(dna_state(), dna_state()) :: :stable | {:regression, map()}
  def detect_regression(previous, current) do
    case current.quality_score < previous.quality_score do
      true ->
        {:regression, %{
          previous_score: previous.quality_score,
          current_score: current.quality_score,
          delta: previous.quality_score - current.quality_score,
          degraded_domains: find_degraded_domains(previous.domains, current.domains)
        }}

      false ->
        :stable
    end
  end

  @spec find_degraded_domains(map(), map()) :: [atom()]
  defp find_degraded_domains(previous, current) do
    previous
    |> Enum.filter(fn {domain, prev_count} ->
      curr_count = Map.get(current, domain, 0)
      curr_count > prev_count
    end)
    |> Enum.map(fn {domain, _} -> domain end)
  end
end

Mandatory Regression Test Protocol

Every bug fix in the platform must include regression tests. This is a P0 absolute requirement with no exceptions:

defmodule PrismaticQuality.RegressionTestProtocol do
  @moduledoc """
  Enforces the mandatory regression test protocol.
  Every bug fix MUST include tests that would have caught the bug.
  """

  @type protocol_result :: :compliant | {:non_compliant, [violation()]}
  @type violation :: %{
    requirement: String.t(),
    status: :missing | :incomplete,
    message: String.t()
  }

  @spec validate_fix_commit(String.t()) :: protocol_result()
  def validate_fix_commit(commit_sha) do
    violations =
      [
        check_test_files_modified(commit_sha),
        check_test_covers_fix(commit_sha),
        check_regression_report(commit_sha)
      ]
      |> Enum.reject(&is_nil/1)

    case violations do
      [] -> :compliant
      violations -> {:non_compliant, violations}
    end
  end

  @spec check_test_files_modified(String.t()) :: violation() | nil
  defp check_test_files_modified(commit_sha) do
    {output, 0} = System.cmd("git", ["diff-tree", "--no-commit-id", "--name-only", "-r", commit_sha])

    files = String.split(output, "\n", trim: true)
    has_test_files = Enum.any?(files, &String.contains?(&1, "/test/"))

    unless has_test_files do
      %{
        requirement: "regression_test_included",
        status: :missing,
        message: "Bug fix commit must include test file modifications"
      }
    end
  end

  @spec check_test_covers_fix(String.t()) :: violation() | nil
  defp check_test_covers_fix(_commit_sha) do
    # Verifies test files test the same modules as modified source files
    nil
  end

  @spec check_regression_report(String.t()) :: violation() | nil
  defp check_regression_report(commit_sha) do
    {message, 0} = System.cmd("git", ["log", "--format=%B", "-1", commit_sha])

    unless String.contains?(message, "REGRESSION TEST REPORT") do
      %{
        requirement: "regression_report",
        status: :missing,
        message: "Bug fix commit message must include REGRESSION TEST REPORT"
      }
    end
  end
end

Comparison with Alternatives

Prismatic QA vs Traditional QA Teams

AspectPrismatic Automated QATraditional QA Team
Detection timingImmediate (pre-commit)Hours/days after development
Consistency100% consistent enforcementVaries by reviewer
CoverageEvery commit, every fileSample-based review
CostFixed infrastructure costScales with team size
BypassNon-bypassable (--no-verify forbidden)Process can be skipped under pressure
Quality domains13 automated domainsTypically 3-5 manual domains
Regression preventionAutomatic via Quality DNAManual regression testing

Prismatic QA vs Continuous Integration Only

CI-based quality checks run after code is pushed, creating a feedback loop of minutes to hours. The Prismatic approach moves quality enforcement to pre-commit, catching violations before they enter the repository. CI remains as a secondary enforcement layer, but the primary feedback loop operates at the developer's workstation, reducing the cost of fixing violations from context-switch overhead to immediate correction.

Quality Score: Prismatic vs Industry Benchmarks

MetricPrismatic PlatformIndustry Average
Quality score100/10060-75/100
Technical debt0 QDP15-40% of codebase
Compilation warnings050-500+
Type coverage100% @spec10-30%
Test coverage80%+40-60%
Quality domains tracked132-4

Best Practices

1. Automate Everything That Can Be Automated

Human quality checks are inconsistent, slow, and subject to fatigue and bias. Automate every quality criterion that can be expressed as a deterministic check. Reserve human review for genuinely subjective decisions (architecture, naming, design).

2. Fail Fast and Loud

Quality violations should be detected as early as possible (pre-commit, not post-deploy) and reported with clear, actionable messages. A quality check that silently passes or produces ambiguous output is worse than no check at all.

3. Make Quality Non-Bypassable

If developers can skip quality checks under time pressure, they will. The platform explicitly forbids --no-verify and treats its use as an L4 violation. Quality gates must be infrastructure, not suggestions.

4. Track Quality Over Time

Point-in-time quality checks miss trends. The Quality DNA system persists quality state across sessions, enabling regression detection and long-term trend analysis.

5. Define Quantitative Standards

"High quality" is meaningless without numbers. Define specific thresholds: 0 compilation warnings, 100% @spec coverage on public functions, <250ms page load, 80%+ test coverage. Quantitative standards enable automated enforcement.

6. Address Root Causes, Not Symptoms

When a quality violation is detected, fix the underlying design flaw rather than adding a workaround that satisfies the check. The mandatory regression testing protocol ensures that every fix is accompanied by tests that prevent recurrence.

Common Pitfalls

Quality Theater

Maintaining impressive metrics without genuine quality. For example, achieving 100% code coverage by writing trivial tests that exercise code paths without validating behavior. The Prismatic approach combats this through property-based testing, mutation testing concepts, and meaningful assertion requirements.

Gatekeeping Without Value

Quality checks that consistently produce false positives or block developers without catching real issues erode trust in the QA system. Every quality gate must demonstrably prevent actual defects. Gates that only annoy developers should be removed or refined.

Treating QA as Someone Else's Problem

When quality assurance is delegated to a separate team, developers stop thinking about quality during development. The Prismatic model embeds quality enforcement into the development workflow, making quality a developer responsibility enforced by automation.

Inconsistent Standards Across Components

Applying different quality standards to different parts of the codebase creates confusion and maintenance burden. The Universal App Quality Standard ensures consistent quality requirements across all 115 umbrella applications.

Neglecting Non-Functional Quality

Focusing exclusively on functional correctness while ignoring performance, security, maintainability, and observability. The 13-domain quality model ensures comprehensive coverage beyond functional testing.

Use Cases

Zero-Warning Compilation Enforcement

The --warnings-as-errors flag treats every Elixir compilation warning as a blocking error. This prevents the gradual accumulation of warnings that eventually mask real problems. The platform maintains zero warnings across all 115 umbrella apps.

Forbidden Patterns Prevention

The forbidden patterns scanner blocks mocks in lib/ directories, stub implementations, placeholder comments (TODO, FIXME, HACK, WORKAROUND, XXX), and naive implementations. These patterns indicate incomplete or temporary code that should never reach production.

Quality Gate Pipeline

The mix quality.gates command runs all quality checks in a single invocation, providing a unified pass/fail result. This is used in CI/CD pipelines and as a quick developer validation command.

Cross-Session Quality Continuity

The Quality DNA system saves quality state to .claude/quality-dna/current-state.json in each app directory, enabling detection of quality regressions across development sessions even when different developers or AI agents work on the codebase.

Regression Test Enforcement

The Mandatory Regression Test Protocol ensures every bug fix includes tests that would have caught the original bug, verifies the test fails before the fix and passes after, and includes a structured report in the commit message.

  • Quality Gate: Automated enforcement checkpoints that block non-compliant code
  • Quality Standard: Universal quality requirements for all umbrella applications
  • Quality DNA: Cross-session quality state persistence and regression detection
  • Code Quality: Source code characteristics measured by static analysis
  • Regression Testing: Mandatory tests accompanying every bug fix
  • Clean Run: Zero-warning compilation standard across all apps
  • Code Coverage: Line and branch coverage metrics for test suites
  • Credo: Elixir static analysis tool for code quality checking
  • Dialyzer: Erlang/Elixir type checker and discrepancy analyzer
  • Quality Floor Guardian: Autonomous quality monitoring system
  • Testing: ExUnit-based test framework and strategy
  • Static Analysis: Compile-time code analysis for defect detection

See Also


Connect & Contribute

Created by Tomas Korcak (korczis) | Open Source under GHL