Rubric version: v3 · Last updated 2026-08-16
This document exists so that any score ActiScore produces can be explained, challenged and independently verified. If a prospect disputes a grade, this is the page to hand them.
It is deliberately explicit about which checks rest on published standards, which rest on industry convention, and which are ActiScore's own editorial judgment. Conflating those three is how scoring tools lose credibility.
---
1. How a score is produced
``` fetch page → extract signals → apply deduction table → weight categories → grade (deterministic) (deterministic) ```
1. The target page is fetched server-side once, following up to 5 redirects, over ports 80/443 only, capped at 5 MB, HTML content types only. 2. Signals are extracted from the response body and headers — presence, counts and lengths of well-known tags and headers. No JavaScript is executed and the page is not rendered. 3. Each category starts at 100. Every failed check subtracts a fixed deduction. Category scores are floored at 0. 4. The composite is a weighted mean of the six category scores, rounded. 5. Letter grades are assigned from fixed score bands.
Scores are pure arithmetic. A language model writes the explanatory text and the remediation advice, and is given the already-final scores with instructions that it may not change or reinterpret them. It has no path to alter a number.
2. Reproducibility
Verified: 25 real domains × 200 iterations = 5,000 scoring runs produced byte-identical output every time. Scoring is also insensitive to the key order of the input object. There is no use of the clock, randomness, locale formatting or environment state anywhere in the scoring path.
The honest caveat. That guarantee is *"same signals → same score."* It is not *"same website next week → same score,"* because the website controls what it returns. A CDN node serving a different `Cache-Control`, an A/B test, geographic routing, bot detection or personalisation can all change the signals legitimately. In practice two independent live captures of all 25 baseline domains produced identical scores — encouraging, but that is two samples, not a study.
Every scan is stored with its full result and its rubric version, so any past score can be reconstructed exactly and shown to have been computed correctly.
3. Category weights
| Category | Weight |
|---|---|
| Security | 26% |
| SEO | 17% |
| Performance | 16% |
| Mobile Usability | 12% |
| Local SEO | 9% |
| AEO | 8% |
| Accessibility | 8% |
| Best Practices | 4% |
These weights are ActiScore's editorial judgment, not an industry standard. No standards body publishes a weighting for website quality. Security leads because ActiScore is used by managed service providers whose value to a client is security posture first. A marketing agency would reasonably weight this differently, and the weights live in one constant so they can be changed.
Before v2 the composite was an unweighted mean of six categories. That let a nearly empty page score 58 (a D) by riding a fast, unremarkable Performance score and a Best Practices score it could not fail.
4. Grade bands
| Grade | Composite |
|---|---|
| A | 93–100 |
| B | 80–92 |
| C | 65–79 |
| D | 45–64 |
| F | 0–44 |
These bands are arbitrary — they mirror US academic grading because it is instantly legible, not because it is derived from data.
They are also demonstrably harsh. Across 25 real sites including Stripe, GitHub, Cloudflare and the New York Times, exactly one reached an A. Stripe scores 84. This is a known open question, recorded rather than hidden.
5. The checks
Each check lists the maximum deduction and its basis:
- Standard — a published specification or documented platform requirement.
- Convention — widely-followed industry practice with no published authority.
- Judgment — ActiScore's own choice.
Deduction *magnitudes* are judgment in every case, even where the check itself is a standard.
Security — 26% of composite
Scored two ways, and the report always says which. When DNS and TLS have been measured for a domain the full rubric below applies and the category is marked measured. On a cold first scan — before the background lookup has landed — only the response headers are available, the original header-only weights apply, and the category is marked inferred with a note saying so. A settled report is always measured, because the settle pass performs the lookups itself.
Full rubric (measured). Deductions total exactly 100.
| Check | Max ded. | Basis | Notes |
|---|---|---|---|
| HTTPS | 18 | Standard | Scheme of the final URL after redirects |
| DMARC Policy | 16 | Standard — RFC 7489 | `p=reject` passes; `p=quarantine` −4; `p=none` −12 as monitoring, not protection; absent −16 |
| SPF Record | 12 | Standard — RFC 7208 | Scored on the `all` qualifier: `-all` passes, `~all` −4, `?all`/`+all`/absent qualifier −9, no record −12 |
| Content-Security-Policy | 10 | Standard — W3C CSP | Presence only; policy strength is not assessed |
| Strict-Transport-Security | 8 | Standard — RFC 6797 | Presence only; `max-age` value not assessed |
| TLS Certificate | 8 | Standard — RFC 5280 | Expired or unreachable −8; judgment: within 21 days of expiry −5 |
| DKIM Signing | 6 | Standard — RFC 6376 | Judgment: nine common selectors are probed. A miss means none of those answered, not that DKIM is absent — which is why it is weighted below SPF and DMARC |
| X-Frame-Options | 5 | Standard — RFC 7034 | Passes on `SAMEORIGIN` or `DENY` |
| X-Content-Type-Options | 5 | Standard — WHATWG Fetch | Passes when it contains `nosniff` |
| Software Version Disclosure | 5 | Judgment | Fires when `Server` or `X-Powered-By` contains a version number |
| Referrer-Policy | 4 | Standard — W3C | Presence only |
| Permissions-Policy | 3 | Standard — W3C draft | Presence only |
Header-only rubric (inferred). HTTPS 30, CSP 20, HSTS 15, X-Frame-Options 10, X-Content-Type-Options 8, Referrer-Policy 7, Permissions-Policy 5.
A check that could not be completed never deducts. An unreachable resolver is reported as *not checked* and scored as zero deduction — it is not evidence about the domain. This is distinguished from an authoritative "no such record", which is a real finding and is scored as one.
Every check here is a published specification, and every one is independently verifiable — `curl -I https://example.com` confirms the headers, and `dig TXT example.com` / `dig TXT _dmarc.example.com` confirm the mail records.
Limitations. These are header *presence* checks, not a security assessment. A site can score 100 here and still be vulnerable — this does not test for injection, authentication flaws, outdated software, open ports or anything requiring active probing. It is a hygiene check on what the server declares.
`X-Frame-Options` is superseded by CSP `frame-ancestors`; it is still checked because it remains widely deployed and cheap to verify.
Removed in v3: the cookie consent banner check. It was inferred from page markup, which is unreliable, and its presence is a privacy-compliance signal rather than a security control. Its weight moved onto `X-Content-Type-Options` and `Referrer-Policy`, both of which are objectively verifiable.
SEO — 20% of composite
| Check | Max ded. | Basis | Notes |
|---|---|---|---|
| Title tag | 20 | Standard (presence) / Convention (length) | Full marks at 45–65 characters |
| Meta description | 15 | Convention | Full marks at 120–160 characters |
| Single H1 | 15 | Convention | HTML5 permits multiple H1s in sectioning content |
| Canonical URL | 10 | Standard — RFC 6596 | Presence only |
| Image alt text | 10 | Standard — WCAG 1.1.1 | Scaled: ≤2 missing is a partial deduction |
| Open Graph tags | 5 | Convention — Open Graph protocol | `og:title` presence |
| Schema markup | 15 | Standard (schema.org) / Judgment (count) | Full marks at 5+ distinct types |
Limitations. The character-length windows are the softest numbers in the whole rubric. Google has never published a title or description length limit; these ranges come from observed search-result truncation, which varies by device and query. A page outside the window is not penalised as failing — it takes a partial deduction.
"5 or more schema types earns full marks" is ActiScore's choice, not a schema.org requirement.
Performance — 20% of composite
As of v3 this category is measured, not inferred, whenever PageSpeed data is available. Every report states which of the two produced the score.
#### Measured path — real Core Web Vitals
Metrics come from the PageSpeed Insights API, using real Chrome user data (CrUX) where the site has enough traffic to appear in it, and a Lighthouse lab run otherwise. Thresholds are Google's published Core Web Vitals bands, not ours:
| Metric | Good | Needs improvement | Poor | Deduction |
|---|---|---|---|---|
| Largest Contentful Paint | ≤ 2.5 s | ≤ 4.0 s | > 4.0 s | 0 / 15 / 30 |
| Cumulative Layout Shift | ≤ 0.1 | ≤ 0.25 | > 0.25 | 0 / 10 / 20 |
| Interaction to Next Paint | ≤ 200 ms | ≤ 500 ms | > 500 ms | 0 / 10 / 20 |
| Total Blocking Time | ≤ 200 ms | ≤ 600 ms | > 600 ms | 0 / 8 / 15 |
This is now the most defensible category in the rubric, because the bands are not ActiScore's opinion — a prospect can check any figure in PageSpeed Insights and get the same answer.
A metric absent from the payload is skipped rather than penalised. INP has no lab equivalent, so a site with no field data would otherwise be marked down for a measurement Google could not take.
#### Inferred path — static fallback
| Check | Max ded. | Basis | Notes |
|---|---|---|---|
| Render-blocking scripts in `<head>` | 30 | Convention | External scripts without `async` or `defer` |
| HTML document size | 30 | Convention | Full raw response size; <75 KB earns full marks |
| Cache-Control | 18 | Standard (header) / Judgment (scoring) | Rewards `max-age`/`public` |
Used when PageSpeed data is unavailable — no API key, a rate limit, a timeout, or the first-ever scan of a domain. The report labels the category "estimated from page structure" in that case, so an estimate is never presented as a measurement.
The gap between the two paths is not small and should not be assumed away. On securafy.com the static path scores Performance 100; real measurement scores it 70, because LCP is 5.0 s. That 30-point difference is why this category was rewritten.
The render-blocking script check is a genuine heuristic — it correctly ignores `async` and `defer` scripts and counts only external ones, which is the classic blocking pattern.
#### Why the first scan of a domain is usually inferred
A PSI call genuinely loads the page in Chrome and takes 20–40 seconds — far too slow to sit in front of a visitor waiting on an embedded grader. So:
- Embed scans read cached PageSpeed data only, and trigger a background
refresh so the *next* scan of that domain is measured.
- Re-scans and batch runs, where nobody is waiting, always fetch live.
- Results are cached per domain for 24 hours.
#### A note on measurement variance
Lighthouse lab runs are not perfectly repeatable: two runs of securafy.com minutes apart returned LCP of 5.2 s and 5.0 s. Scoring in bands rather than continuously absorbs that — both readings are "poor", so the score did not move. Metrics are also rounded before scoring, so float jitter cannot change a result. A metric sitting exactly on a band boundary can still flip between runs; that is inherent to the measurement, not to the rubric.
Mobile Usability — 15% of composite
| Check | Max ded. | Basis | Notes |
|---|---|---|---|
| Viewport meta tag | 40 | Standard — W3C | Requires `width=device-width` |
| Pinch-zoom not disabled | 30 | Standard — WCAG 1.4.4 | Disabling zoom is an accessibility failure |
| Responsive images | 30 | Standard — HTML `srcset` | Presence of responsive image markup |
Limitations. Static analysis only — the page is never rendered on a device or emulator. Tap-target sizing, font legibility and horizontal overflow are not assessed, and those are often what actually makes a site unusable on a phone.
In v2 two checks were removed from this category: Apple touch icon and theme colour. They accounted for 20 of the 100 points and neither affects whether a page works on a handset — both are homescreen and browser-chrome branding.
AEO (Answer Engine Optimisation) — 10% of composite
| Check | Max ded. | Basis |
|---|---|---|
| Structured data coverage | 25 | Judgment |
| FAQ content present | 15 | Judgment |
| Author signals | 10 | Judgment |
| Blog / content presence | 10 | Judgment |
| FAQPage or HowTo schema | 10 | Convention — schema.org types |
Limitations, and this one matters for how the category is described.
This category measures structured-data readiness — whether a page carries the markup and content patterns that answer engines are known to consume. It does not measure whether the brand actually appears in AI-generated answers. No standards body defines "AEO readiness," and no published research fixes these weights.
Tools such as Profound, Peec and Otterly measure real answer-engine visibility by querying the engines. ActiScore does not do that today. Describing this category as AI-visibility measurement would be inaccurate.
*Planned:* a separate, explicitly-labelled measurement of actual answer-engine presence, kept distinct from this on-page readiness score so the two claims never blur.
Best Practices — 5% of composite
| Check | Max ded. | Basis | Notes |
|---|---|---|---|
| Robots meta tag | 20 | Standard | `noindex` is the heavy deduction |
| Twitter Card | 5 | Convention | Presence |
| Link structure | 5 | Judgment | Passes above 10 total links |
Limitations. The 10-link threshold is arbitrary and this category is intentionally the lowest-weighted at 5%.
In v2, Open Graph was removed from this category — it is already scored under SEO and was therefore counted twice — and a "Forms" row was removed because it carried a zero deduction and could never fail. A row a prospect reads that cannot fail is noise.
6. Outreach threshold
A composite below 70 sets a `triggerOutreach` flag, used to mark a scanned site as worth a sales conversation.
This is a sales heuristic, not a quality judgment. It was tuned against the 25-domain baseline: the previous threshold of 80 fired on 72% of real sites including Cloudflare and Mozilla, which makes the signal useless and the sender look uninformed. At 70 it fires on 32%.
7. What ActiScore does not do
Stated plainly, because the absences matter as much as the checks:
- Does not execute JavaScript or render the page. A single-page app that
builds its head tags client-side will score poorly on signals that are present after hydration.
- Does not measure speed. See Performance above.
- Does not crawl. One URL per scan; interior pages are not assessed.
- Does not perform a security assessment. Header hygiene only — no active
probing, port scanning, vulnerability testing or credential checks.
- Does not measure AI-answer visibility. See AEO above.
- Does not judge content quality, design, or commercial effectiveness.
8. Change control
The rubric is changed in place rather than versioned per tenant, because Securafy's public grader and every tenant embed share one scoring core — a fix should reach both at once.
To make that safe:
- 25 real domains have their extracted signals and resulting scores frozen as a
committed baseline, spanning grades A through F and including Securafy's own sites and real prospect domains.
- An automated gate fails the build on any scoring change, printing a per-domain,
per-category old-to-new table.
- A rubric change is reviewed against that table before deploy, and any
letter-grade move on a live prospect domain is flagged individually.
- Every stored scan records the rubric version that produced it, and the history
view refuses to silently compare scores computed under different versions.
The v1→v2 delta table is recorded in `docs/plans/2026-08-15-rubric-change-delta.txt`.
9. Open questions
Tracked honestly rather than quietly:
1. Grade A is close to unreachable — 1 of 25 real sites. The A boundary may need lowering or the deduction tables loosening. Still open. 2. Length windows for title and meta description rest on the softest evidence in the rubric and deserve a review against current search-result behaviour. 3. AEO should be renamed to reflect that it measures readiness, not visibility — pending a separate, real answer-engine visibility measurement.
Closed in v3: Performance moved from inference to measurement; the favicon check left Performance and the cookie-banner check left Security.