Static Analysis Policy
A requirement describes what a system must do. A Static Analysis Policy describes how clean its code must be before that work is considered finished — which automated checks run, how strict each one is, and how findings are severity-rated and reported. In REQQA this is more than internal housekeeping: a release can carry a machine-readable policy that tells an automated builder exactly which analyses to run, which thresholds to meet, and how to report results back. It is the quality-and-security baseline a release is built against.
This chapter explains the model. For the click-by-click steps, see How to configure static analysis.
Everything here is organisation-scoped. Each organisation owns its own catalogue, knowledge base, and severity map; they are seeded on demand from a built-in canonical set and then maintained by the organisation's analysts. Nothing is shared between organisations.
Why REQQA has this
A requirements tool that only checks prose would stop at the boundary of the code. The Static Analysis Policy carries quality intent across that boundary. It exists so that:
- REQQA's own development has a consistent baseline that ratchets up over time rather than drifting — checks get stricter release by release, never quietly looser.
- A release can publish a precise, testable definition of "statically clean" for whoever builds it — including the autonomous builders described in The Dark Factory.
- Findings from many different tools are normalised onto one severity scale, so a release's quality can be measured rather than asserted.
The guiding principles are conservative: the stricter setting wins when in doubt, every required check should fail loudly, gates are layered (developer pre-commit, authoritative CI, release-time), and every suppression needs a written justification.
The three building blocks
The policy is assembled from three independently maintained pieces, plus the per-release profile that ties them to a specific release.
1. Categories — the catalogue of checks
A category is one class of static check: "Type safety", "Secrets scanning", "Accessibility",
and so on. Categories live in the sa_categories table and are organised into six domains:
| Domain | Name |
|---|---|
| A | Source code quality |
| B | Security (code-level) |
| C | Supply chain & dependencies |
| D | Frontend & UX |
| E | Interface contracts |
| F | Process hygiene |
The built-in canonical set has 34 categories (codes A1 through F5), and each carries
sensible defaults that an organisation inherits when it seeds the catalogue:
- a default tool (for example,
mypyfor type safety,bandit + semgrepfor Python security scanning,axe-core + pa11y-cifor accessibility); - a default gate — the layer the check belongs at (
pre-commit,ci,release, or a combination); - a default threshold — the pass condition in words (for example, "zero High; Medium triaged before release");
- a severity floor —
HIGH,MEDIUM, orLOW, the minimum severity at which this category's findings matter; - a default status — the category's starting point on the adoption ladder (see Adoption status below).
Analysts manage their catalogue on the Category catalogue page (saCategoryList). They can add
new categories, edit any field, and archive a category they don't use without deleting it (an
archived category simply stops appearing in active lists and profiles). Category codes are unique
within an organisation. Domain D7 is a special pointer to B4 (template safety) and is never
gated in its own right.
2. The knowledge base — rules and guidance
Where a category names a tool, the rule knowledge base (sa_kb) holds entries for the
individual rules those tools emit — for example Bandit's B608 ("Hardcoded SQL expressions") or
Ruff's F401 ("Unused import"). Each entry records:
- the tool and the rule code (unique together within the organisation);
- a plain-language name and explanation of what the rule detects;
- a risk note — why the finding matters;
- remediation hints — how to fix it;
- an upstream URL to the tool's own documentation;
- the rule's native severity as the tool reports it, and its normalised severity on REQQA's
HIGH/MEDIUM/LOWscale; - the category code the rule maps to.
The knowledge base turns a terse tool diagnostic into something a person — or a builder — can act
on without leaving REQQA. Analysts manage it on the Rule knowledge base page (saKbList), which
supports filtering by tool, by normalised severity, and free-text search across rule codes and
names. The built-in seed ships a representative starter set; an organisation curates its own
remediation guidance on top over time.
3. Severity mappings — one scale across many tools
Different tools speak different severity dialects: Bandit says HIGH/MEDIUM/LOW, mypy says
error/note, ESLint says 2/1, axe-core says critical/serious/moderate. The severity
map (sa_severity_map) translates each tool-native severity into REQQA's single
HIGH/MEDIUM/LOW scale, so findings from every source can be compared and counted together.
Each mapping is a (tool, native severity) → normalised severity rule. Analysts manage them on the
Severity map page (saSeverityList). When REQQA needs to normalise a value, an
organisation-specific mapping always wins over a system default, and an unmapped severity falls back
to MEDIUM (a documented, conservative default rather than silently dropping the finding).
Applying a policy to a release
Categories, knowledge base, and severity map are the library. A release needs a configuration —
which checks apply to this release, and how strict each one is. That configuration lives in
sa_profiles: one row per active category, per release. (In the data model a release is a
scope, so the per-release rows are keyed by scope_id.) You reach it through the Static
Analysis tab on the release view.
Profiles set the baseline
Rather than configuring 34 categories by hand, you apply a named default profile that sets every
category's starting disposition in one action (the saApplyProfile action). There are four:
- Strict — every category is
required. - Standard — domains A, B, C and D are
required; E and F areadvisory. This is the default starting point. - Minimum — only a core set is
required(A1,A2,A3,B1,B2,C1); everything else isexcluded. - Custom — everything starts
advisoryfor the analyst to configure by hand.
Each category's disposition is one of:
- required — the check must pass for the release to be clean;
- advisory — the check runs and is reported, but does not block;
- excluded — the check does not apply to this release.
Re-applying a profile is a disposition reset, not a wipe. Switching from Standard to Strict re-baselines each category's required/advisory/excluded setting, but it preserves any per-category progress status and any threshold or severity overrides you've set. You can change your mind about the profile without losing your work.
Fine-tuning per category
On top of the profile baseline, you can tune each category for this release (saved in bulk by the
saProfileSave action):
- set its in-release status —
not_started,in_progress,passed, orwaived— to track progress against the release; - override the default threshold or severity floor for this release only. An override demands a written rationale — REQQA will not record an override with an empty rationale, so every deviation from the catalogue default carries its justification with it.
Seeding when the catalogue is empty
A brand-new organisation starts with an empty catalogue. The seed actions load the canonical defaults so there is something to configure:
- Seed the catalogue (
saCategorySeed, orsaSeedForScopedirectly from the Static Analysis tab) loads the 34 canonical categories. - Seed the knowledge base (
saKbSeed) loads the starter rule set. - Seed the severity map (
saSeveritySeed) loads the default tool-native mappings.
All three are idempotent: existing entries are left untouched and only missing ones are added, so re-seeding never clobbers an analyst's edits. Each reports how many entries it added and how many it skipped.
Adoption status — the ratchet
Switching on all 34 checks at once against an existing codebase would bury genuine signal under thousands of pre-existing findings. So each category carries an adoption status that moves it along a five-stage ladder, independently of every other category:
| Status | Meaning |
|---|---|
| baseline | Capture the current findings; nothing is gated yet. |
| stop-the-bleed | Block new violations while tolerating the existing backlog. |
| triage | Classify the backlog into real issues versus false positives. |
| ratchet | Tighten the threshold step by step as the backlog shrinks. |
| lock | Fully enforced; no regressions tolerated. |
At any moment, categories sit at different stages — style checking might be at lock while a newer
check like AI/prompt safety is still at baseline. The status is reviewed at each release closure
and advanced (or, exceptionally, rolled back) based on the evidence the closing release produced.
This is the policy's central discipline: ratchet, don't paper over — adopt a check, fix the
worst class of finding, tighten, repeat; never normalise a long-running suppression.
How it fits the workflow
The Static Analysis Policy is the code-quality counterpart to the prose-quality work that the analysis engine and the DeFOSPAM technique do on requirements and stories. Requirements analysis makes the specification defensible; the static analysis profile makes the resulting build defensible. Together they let a release be handed to a builder — human or automated — with an unambiguous, testable definition of "done" at both ends.
Related
- How to configure static analysis — the step-by-step recipe.
- Releases and scopes — what a profile attaches to.
- The Dark Factory — why a machine-readable policy matters.
- Analyser codes — the DeFOSPAM codes used for requirements analysis.