Skip to content

Pin the site's ten heaviest claims to the code that makes them true - #327

Merged
Apolloccrypt merged 4 commits into
mainfrom
chore/site-claims-tests
Sep 2, 2026
Merged

Pin the site's ten heaviest claims to the code that makes them true#327
Apolloccrypt merged 4 commits into
mainfrom
chore/site-claims-tests

Conversation

@Apolloccrypt

@Apolloccrypt Apolloccrypt commented Sep 2, 2026

Copy link
Copy Markdown
Owner

What

The pruning plan of 1 September (Snoeiplan, "wat er juist bij moet", point 3) asks that every promise on the site be nailed to a test that fails when the promise stops being true. The gate existed (seo-contract.test.mjs, ui-truthfulness.test.mjs) but covered markup and two pages. This PR adds tests/site-claims.test.mjs: ten tests that read the constant, name or count from the source that enforces it and check the page says the same thing. Node builtins only, so it runs in the existing "Root integration suites" job.

The full inventory (every factual claim on the 38 public pages, with the test that pins it or UNCOVERED) is in docs/site-claims.md so the follow-up is visible.

What turned out to be wrong when measured

Page said Code shows Change
"The relay loads 3 KEMs and 18 signatures" (security, crypto-agility, "loaded in production") relay/crypto/bootstrap.js registers 3 KEMs and 17 signatures, and only in CRYPTO_MODE=extended; .env.example documents core (ML-KEM-768 + ML-DSA-65) as the default 17, plus the mode caveat
"After ten consecutive failures, an account locks for thirty minutes" (security) No lockout in admin/server.js or relay/relay.js Sentence removed; the limits that do exist (5/IP and 10/email per 15 min, session 1 h, TOTP 30 s) are pinned
Enterprise "SLA 99.9%" (pricing) sla.html commits to 99.95% pricing now quotes the SLA page
"measured from multiple EU locations, every 60 seconds"; "Historical uptime data is published in the CT log" (sla) The only external check is .github/workflows/product-heartbeat.yml, hourly: a browser run of paramant.app plus the transfer and ParaSign canaries against relay.paramant.app. None of the three calls GET /health; the four sector relays are not in the run. /status (frontend/js/status.inline1.js) is the only thing that fetches /health, on all five relays, from the visitor's browser. The CT log holds key and transfer commitments Describes exactly those two things: what the hourly run exercises, that the sector relays are not in it, and what /status does
"Twenty findings across two audits, fully resolved before public release" (press) /docs#audits table: three audits, 14 + 6 + 20 = 40 findings, 4 critical; docs/security-audit-2026-04.md still marks findings 4, 6 and 14 as in progress or open Forty across three; "fully resolved" dropped
trust linked to /security#audits ("the findings and patch timeline are on the security page"); dpa linked to plain /security ("see security audit summary") /security has no id="audits", so the trust link lands at the top of a page without the table; the table is id="audits" in frontend/docs.html Both now point at /docs#audits, and the trust link text reads "docs page"
"IP logging: Nginx access logs, retention 7 days, logs follow the server's log rotation" (security) No log-rotation config in deploy/; nginx-paramant-live.conf has access_log off on every server block that serves the site or a relay Says access logging is off in that configuration; no retention period, no log rotation

What is pinned and now goes red

  1. ML-KEM-768 / ML-DSA-65 are what core mode registers (bootstrap.js, impls/*), and every page that names a set names these.
  2. Algorithm counts equal registerKEM/registerSig calls; a stale count anywhere on the page fails.
  3. AES-256-GCM, HKDF-SHA256, SHA3-256, PBKDF2 from encryption.js, crypto-wasm/src/lib.rs, ct-hash.js, sign-flow.js, vault.js.
  4. 5 MB from MAX_BLOB default and tiers.js file_mb.
  5. Link expiry 1 h / 24 h / 7 d from tiers.js view_ttl_ms.
  6. Session, TOTP step and login limits from admin/server.js and totp.js; the lockout sentence stays out until code exists.
  7. Audit counts from the /docs#audits table and the audit document's summary line; "fully resolved" is forbidden while that document lists open findings.
  8. Pricing SLA figure equals sla.html; the measurement text may only describe what exists: the live job runs the three suites against paramant.app and relay.paramant.app (read from the workflow), none of them and no workflow step calls /health or a sector host, and status.inline1.js fetches /health on five sectors. The page must say the sector relays are not in the hourly run and that /status polls all five from the browser.
  9. Every access_log in nginx-paramant-live.conf is off and every site or relay block carries it; the row cites that file; no retention number without a rotation config.
  10. "10 encrypted CLI tools" equals the developer-tools.js catalogue.

Mutation check (locally, reverted): changing "17" to "18" on crypto-agility, 99.95 to 99.9 on pricing, "one hour" to "two hours" on security, the heartbeat cron to daily, and the press count back to "twenty across two" each turn the corresponding test red. After the review fix, eight more, each failing exactly the intended test: /sla claiming "automated HTTP health checks" (8), a curl health.paramant.app/health step added to the live job (8), fetch(RELAY + '/health') added to the transfer canary (8), a sector removed from status.inline1.js (8), the sector caveat removed from /sla (8), one access_log off changed to a log path (9), one access_log off deleted (9), the old IP-logging row restored (9).

Second review round: the comment stripper

The reviewer found that test 8 read its sources through src.replace(/\/\/.*$/gm, '').
That cuts every line at its first //, including the one inside a URL, so
const RELAY = (process.env.PARAMANT_RELAY_URL || 'https://relay.paramant.app')
in tests/transfer-canary.test.mjs became const RELAY = (process.env.PARAMANT_RELAY_URL || 'https:.
The assertion under it (the canaries must not call /health) was therefore
reading a truncated file: a health check written as a full URL would have been
stripped away before the match ran, and the test would have passed on nothing.

Fixed in 76fdcc2 with a string-aware walk (stripJsComments) instead of a
regex: string, template and regex literals are copied through untouched, //
and /* */ are dropped. Node builtins only, so the suite still needs no parser
dependency. The workflow YAML gets the same treatment through YAML's own rule,
where # only opens a comment at the start of a line or after whitespace.
The two other uses of the old regex, on relay/crypto/bootstrap.js in tests 1
and 2, were switched over as well.

Sabotage proof, all reverted:

Sabotage Old stripper New stripper
await fetch('https://relay.paramant.app/health') added to the transfer canary test 8 green (the line was cut at https:) test 8 red, "tests/transfer-canary.test.mjs now calls /health"
pricing quotes 99.9% while sla.html commits to 99.95% red red
the sector caveat removed from /sla red red

Tests

Re-run on 76fdcc2, rebased on main (which now carries #324 navigation,
#326 billing brake, #329 rust-1.98 binding and #330 signals). The rebase was
clean and python3 frontend/apply-nav.py reported no change, so the pages on
this branch already carry the #324 nav.

  • node --test tests/site-claims.test.mjs: 10/10 pass
  • Root integration suites as CI runs them (grep -L "from 'playwright'" tests/*.mjs, canaries excluded, includes links, seo-contract, ui-truthfulness, site-claims and frontend-loading-contract): 120 tests, 118 pass, 2 skipped, 0 fail
  • bash tests/static-sanity.sh: PASS
  • bash scripts/check-commit-style.sh: OK

Not in this PR

  • No nav, no page structure, no new pages. frontend/apply-nav.py and frontend/js/nav-auth.js untouched (spoor B2).
  • Three contradictions the code cannot settle, listed in docs/site-claims.md for a decision: the BUSL Change Date (LICENSE says 2029-01-01, license.html and terms.html say 2030); the /docs#audits "All resolved" rows versus the audit document; and the audit totals (the /docs#audits table, which press, trust and the DPA are pinned to, counts 40 findings across three audits, while SECURITY.md, which the table cites as the full record, lists four review sections: RAPTOR 10, Zwarts 14, Zwarts 6, Williams 20 = 50).
  • Whether the nginx on the server matches deploy/nginx-paramant-live.conf is not provable from the repository (SECURITY.md says the edge-facing config in sites-enabled/ is not in git; nginx-paramant-public.conf carries no access_log directive). Noted in docs/site-claims.md; not checked on production, which this branch does not touch.
  • The remaining norm claims on pricing (NIS2 "by design", NEN 7510 / eIDAS / IEC 62443 mappings) are the largest uncovered claim left after Snoei: 17 normen- en sectorpagina's van paramant.app #323 and are flagged, not changed.

Apolloccrypt and others added 4 commits September 2, 2026 16:15
The pruning plan of 1 September asks that every promise on the site be
nailed to a test that fails when the promise stops being true. The gate
existed (seo-contract, ui-truthfulness) but only covered markup and two
pages. tests/site-claims.test.mjs now reads the constant, name or count
from the source that enforces it and checks the page says the same thing.

Four claims turned out to be wrong when measured, and the pages change
to what the code shows:

- security and crypto-agility said "3 KEMs and 18 signatures loaded in
  production"; bootstrap.js registers 17 signatures, and only in
  CRYPTO_MODE=extended. The default is core, which loads ML-KEM-768 and
  ML-DSA-65. Both pages now say so.
- security promised a 30-minute account lockout after ten failures; no
  such code exists (admin/server.js, relay.js). Sentence removed. The
  numbers that do exist (session 1 h, TOTP 30 s, 5/IP and 10/email per
  15 min) are pinned.
- pricing quoted a 99.9% Enterprise SLA; sla.html commits to 99.95%.
  The SLA page also described a 60-second probe from multiple EU
  locations and uptime history in the CT log; neither exists. It now
  describes the hourly product-heartbeat workflow and /status.
- press said twenty findings across two audits, fully resolved before
  public release. The /docs table lists three audits and forty findings,
  and docs/security-audit-2026-04.md still marks three as in progress or
  open. trust and dpa linked to /security#audits, which does not exist;
  they now point at /docs#audits.

security's "IP log retention: 7 days" had no rotation config behind it
and now promises no period. The rest of the inventory, 60-odd claims
with their status, is in docs/site-claims.md so the follow-up is visible.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJk2nCLCLmi3F71qkCUn7N
…ogging

The measurement paragraph on /sla, new in this branch, said availability is
measured with hourly GET /health checks from the product-heartbeat workflow.
That workflow never calls /health: it loads paramant.app in a browser and
pushes a transfer and a signature through relay.paramant.app, and the four
sector relays are not in it. The only thing that polls /health on all five
relays is /status, from the visitor's browser. The paragraph now says exactly
that, and test 8 reads the live job (suites, targets, no /health, no sector
host) and status.inline1.js (five sectors, fetch /health) so the text has to
change when the monitoring does.

The IP-logging row on /security still described nginx access logs that
follow log rotation while deploy/nginx-paramant-live.conf switches access
logging off on every server block that serves the site or a relay. The row
now says that, and test 9 fails on any access_log that is not off. Whether
the server matches the file is noted as unprovable from the repository.

docs/site-claims.md gains the third contradiction the audit numbers expose:
the /docs#audits table counts 40 findings across three audits, SECURITY.md
lists 50 across four.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJk2nCLCLmi3F71qkCUn7N
The line-comment regex cut every source line at its first "//", so
'https://relay.paramant.app/health' in tests/transfer-canary.test.mjs
became 'https:'. Test 8 asserts the canaries do not call /health; against
a health check written as a full URL it was asserting on a truncated
file. Walk the source instead and copy string, template and regex
literals through untouched. Same rule for the workflow YAML, where "#"
only opens a comment at the start of a line or after whitespace.

Proof: adding fetch('https://relay.paramant.app/health') to the transfer
canary now fails test 8; with the old stripper the same sabotage passed.
@Apolloccrypt
Apolloccrypt force-pushed the chore/site-claims-tests branch from 038b266 to 76fdcc2 Compare September 2, 2026 14:16
@Apolloccrypt
Apolloccrypt merged commit 6f98bb7 into main Sep 2, 2026
11 checks passed
Apolloccrypt added a commit that referenced this pull request Sep 2, 2026
Run 33624449015 this morning reported all four steps green. Two of them proved
nothing, and the log says so:

  ok 1 - parasign: an envelope is created, signed and notarised # SKIP PARASIGN_CANARY_KEY not set
    duration_ms: 1.284603
  ok 2 - parasign: the signed document comes back stamped # SKIP PARASIGN_CANARY_KEY not set
    duration_ms: 0.115257
  # pass 1
  # fail 0
  # skipped 2

node --test prints a skipped test as `ok N - name # SKIP reason` and counts it
in `# pass`. Both canaries used { skip: KEY ? false : 'reason' }, so a missing
secret meant a skip, a skip meant ok, and ok meant a green tick. The duration is
the part that cannot be argued with: creating an envelope, signing it and
verifying an ML-DSA-65 signature does not happen in 1.28 milliseconds. Nothing
left the runner. The failure filter had the same blind spot from the other side,
scanning for ^(x|not ok|AssertionError|fail) while a skipped test begins with ok.

Neither secret exists. gh secret list returns four names, none of them a canary
key, and there are no environment or dependabot secrets. So the keyed ParaSend
route and the entire ParaSign product have never been checked, not once, by
anything.

THE RULE

A monitoring step may never have an escape hatch. If what it needs is missing,
that is red, with the name of what is missing. A reason belongs in the error
message, not in the verdict.

WHAT IS HERE

scripts/heartbeat/, four steps, no test runner and therefore no pass/skip
semantics in the way:

  surface   /health; /v2/health/deep, whose three possible worlds are told apart
            from the answer (405 mode-dropped, 200 in full mode, 401 behind the
            internal gate) and whose body is asserted because the handler
            answers 200 even when overall is red; and the six hosts x two ParaID
            issuance paths, so the deny inserted by hand into nginx on 01-09 is
            checked hourly from outside. A 400 there means the handler is
            reachable again.

  parasend  both routes, anonymous and keyed. Upload, fetch, compare the bytes,
            require the burn. Plus a payload that does not match its hash, which
            must be refused before storage.

  parasign-receipt      /v1 with the psk_test_ key: envelope to completed, then
            the .psign verified offline - the notary counter-signature against
            the key published at /v2/pubkey rather than the key printed inside
            the receipt, and every party signature against a message rebuilt by
            the relay's own signMessageBytes.

  parasign-public-sign  /v2, the route a recipient's browser actually posts to,
            which the sandbox auto-signer never touches. It submits a signature
            that does not verify and requires a 400 first, because a relay that
            rubber-stamps would sail through every positive check ever written.
            Then it signs for real, verifies offline, and folds the new CT leaf's
            inclusion proof back to the published tree head with ctNodeHash.

Every step writes heartbeat-evidence/<step>.json with ids, hashes, statuses and
timings, uploaded as an artifact on every run. runStep fails a step that recorded
no proof even when nothing threw, so a step cannot pass by doing nothing again.
Failure emits one ::error:: annotation, which GitHub renders above the log, and
opens or comments on a single issue titled "Heartbeat rood"; green closes it.

ONE THING FOUND WHILE BUILDING IT

@noble/post-quantum 0.6.1 takes verify(signature, message, publicKey). The
ParaSign canary called verify(publicKey, message, signature), which throws a
RangeError on the installed version. Even with its secret set it could never
have passed. The argument order now lives behind named wrappers in one place,
with a round-trip check at import.

ALSO

The hourly half of product-heartbeat.yml moves here, so there is one alarm with
one evidence artifact instead of a job whose green tick meant different things in
different steps. product-heartbeat.yml keeps the browser suite as the
pull-request gate. The two canary suites are deleted; their coverage is in
scripts/heartbeat/ and they carried the bug. The name-based exclusion they needed
in test.yml goes with them, which is how a suite ends up running in no workflow
in the first place.

docs/heartbeat.md: what each step proves, which secrets are needed and why, and
how to run it by hand.

ONE CLAIM ON THE SITE HAD TO MOVE WITH IT

site-claims.test.mjs (#327) pins the SLA measurement paragraph to the workflow
that makes it true, and it caught this change, which is exactly what it is for.
The old hourly run deliberately did not call /health and did not touch the
sector relays, and the page said so. The new one calls GET /health and the deep
readiness check, and reaches all six hosts for the ParaID deny check. So the
paragraph now says that, and says plainly that reaching the four sector relays
to confirm a retired route stays refused is not an uptime measurement. The test
moved to heartbeat.yml with assertions in both directions: if a step stops
touching its product, the sentence has to go.
Apolloccrypt added a commit that referenced this pull request Sep 2, 2026
Rebased onto a main that gained the site-claims suite. Two of its ten gates
disagreed with this branch, both because they pinned something this PR changes
on purpose.

Gate 5 pinned the tier name Free in two sentences on /privacy. The numbers it
guards are unchanged and still read out of tiers.js; only the name moved, and
the remaining Free on the retention line moved with it so /privacy does not
carry two names for one plan.

Gate 10 required the homepage to say 'N encrypted CLI tools'. A gate that
forces a page to MAKE a claim is the wrong invariant, and this is the claim the
July audit found unobtainable: paramant-solutions is private, so a visitor
cannot get the tools, and the sentence came off the buyer homepage. Retargeted
to what must actually stay true: any page stating a tool count must state the
catalogue's real one, checked across every page under frontend/ rather than the
homepage alone. Sabotage-checked in both directions: a page claiming 7 fails, a
page claiming 10 passes.

The pricing conflict from the rebase kept both sides: main's correction of the
Enterprise SLA to 99.95 percent, and this branch's tier rename. The homepage
quoted 99.9 percent in two places and now matches.
Apolloccrypt added a commit that referenced this pull request Sep 2, 2026
The three pages a buyer reaches after the homepage were written for someone
who already believes the product. /about opened on the mission and put the
founder in section 03, below the cryptography. /security opened on defence in
depth and kept the one row a buyer actually came for, the jurisdiction table,
seven screens down. /trust never said who it was for.

They now run the order the messaging guide fixes (docs/brand/messaging.md):
what this is, who it is for, who is behind it, a next step, then the proof
including the honest limits.

/about is the founder page.

  The lede is plain language first: sign and send documents so only you and
  the recipient can read them, so anyone can check later that the document is
  genuine, on servers in Germany under EU law. Post-quantum arrives after that
  sentence, as the reason it holds up, not as the opening. Under it, in the
  first phone screen: who it is for (small offices that sign and send
  confidential papers, and the person who has to check a supplier), then Mick
  Beer, privacy and security researcher, founder of Paramantis Solutions B.V.,
  then two buttons. Measured at 390px: the last of those sits at y=665, inside
  the first screen. The section number "00" is hidden in the mobile override
  instead of landing under the H1 as a stray number. Below that, the split
  itself: ParaSign Free and ParaSend Free on one side, ParaSign Pro, Business
  and Enterprise and ParaSend Pro and Enterprise on the other, named and
  priced as /pricing names and prices them. The cryptography section keeps
  every word and moves to 03, where it reads as evidence for the paragraph
  above rather than as the argument itself.

/security answers "why would I trust you" before it answers "how does it work".

  The hero says it without jargon: even if our own server is broken into,
  nobody can read your documents, because they are encrypted on the device
  before they leave it. The eyebrow is EVIDENCE, not DEFENSE IN DEPTH, and the
  word relay no longer appears above the fold undefined. A four-card block
  says what each proof means and links to where the reader checks it. The
  jurisdiction and privacy table moves up under it, in main's wording,
  including the corrected IP-logging row. The EU-law card no longer says the
  CLOUD Act is "not applicable" full stop: it says no US storage and no US
  parent company, and it names the third-party services in front of the site,
  Cloudflare among them, that the out-of-scope list further down already
  carries. The hero promises "who audited it", so the page now answers it:
  three external audits in April 2026, two by R. Zwarts and one by Ryan
  Williams of Smart Cyber Solutions, with the finding counts and resolving
  commits on /docs#audits and the findings in SECURITY.md, and the plain
  statement that the reports themselves are not published. The pointer that
  sent the reader to /architecture for audit history is gone, because
  /architecture sends them back here. The page ends in a next step, two
  buttons, instead of only a disclosure address.

/trust says who it is for, in the hero.

  The lede named the reader's relay before it named the reader. It now opens
  on the audience: organisations running Paramant on their own server, and
  anyone who has to check a supplier, with hosted users pointed at /security.
  The tab title said "Trust & Transparency" while the H1 said "Trust &
  Verification"; they match now. The audit paragraph names its auditors, links
  the table on /docs#audits and SECURITY.md, and says the reports are not
  published. The thirty-one em-dashes are gone, per the house style.

The eIDAS wording is not spread. /about calls a ParaSign signature a Simple
Electronic Signature; the /pricing FAQ calls it advanced (AES). Those are
different levels under eIDAS and settling it needs its own round, so the
sentence stays on the one page that has always carried it. security.html does
not get a second, competing level, and a test now asserts it does not.

No claim on any of the three pages is new. The sentences the copy rests on are
pinned in tests/ui-truthfulness.test.mjs: the founder name and title, the
three /about proof sentences, the SES limit on /about only, the CLOUD Act row,
the same-cryptography-on-every-plan sentence on /pricing and /security, the
certification limit, the audit sentences and the "reports are not published"
line on both pages, the /trust title matching its H1, and what each hero has
to say above the first section heading. tests/site-claims.test.mjs gains an
eleventh block: the tier facts on /about are read out of the /pricing tier
cards (signature allowance, both link expiries, read count, device count,
Enterprise SLA) and asserted, so /about cannot drift from what is sold. That
block also catches the number this branch had wrong: /about said the ParaSend
Enterprise SLA was 99.9%, /pricing and /sla say 99.95%.

Rebased on main, so the four corrections #327 made are kept: 17 signature
schemes with the core/extended caveat, no account-lockout promise, the
IP-logging row citing access_log off in deploy/nginx-paramant-live.conf, and
the /trust audit link pointing at /docs#audits.

Mobile: 390px, no horizontal scroll on any of the three.

index.html, apply-nav.py and js/nav-auth.js are untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJk2nCLCLmi3F71qkCUn7N
Apolloccrypt added a commit that referenced this pull request Sep 2, 2026
main moved while this branch was open. Rebased onto 99909a1 and folded what
arrived into the CHANGELOG section, because a release section that is a
snapshot of the moment it was written is the exact failure this PR is fixing.

New in the section: the ParaSign product page at /parasign (#325), the
messaging guide (#331), the homepage rewrite for a buyer (#328), the site
claims pinned to the code that makes them true (#327), the heartbeat that
cannot be green without evidence (#338), and the route suites that boot a real
relay.js (#341). Count in the intro goes from 268 commits and 52 PRs to 277 and
58, and the intro now says it was rebased rather than pretending it was written
in one pass.

#341 is worth naming for what it is: point 3 of the toekomstbestendigheid
report was that relay.js, 6488 lines and 68 routes, was loaded by no unit test
at all. It is now loaded by the route suites. That point is not closed, but it
is no longer zero.

Two conflicts, both resolved toward main's newer structure:

- product-heartbeat.yml: #338 moved the live job out into heartbeat.yml. Took
  main's file whole and applied the Node 24 bump to both files instead, so the
  bump follows the job rather than the filename. All seven node-version entries
  across the five workflows now read '24'.
- test.yml: #341 added a second silent-suites gate for the route job and set
  both expected sets to empty. Kept main's exclusion lists and applied
  --test-reporter=tap to both steps.

That second one matters more after the rebase than before. With a non-empty
expected list, the spec reporter made the gate fail, which is how it was found.
With an empty expected list, the spec reporter makes it PASS: the grep finds
nothing, silent is empty, and a gate that can no longer see anything reports
green. A dead gate that reports green is precisely what these two gates exist
to prevent, so both now pin the reporter rather than inherit a Node default.

Tests after the rebase, on Node 22 and again under node:24-alpine: relay 175,
admin 40, root 145 (143 pass, 2 pre-existing skips), both silent-suite gates
empty as expected. static-sanity PASS including the style guard, bash -n and
eslint clean. The root job now runs npm ci first, per #338; tests/README.md and
the local commands in docs/RELEASE.md match what CI does.
Apolloccrypt added a commit that referenced this pull request Sep 2, 2026
Second pass on the review of PR #333, rebased on main (#324, #327, #328,
#331, #342).

/docs
- The buyer line is pinned to the visible paragraph. The old assertion
  matched the same words in four meta tags, so deleting the paragraph
  left the test green. Proven by sabotage, both ways.
- The buyer gets a real button to /pricing beside Quick start, same size,
  same row. Quick start stays the primary CTA the messaging guide asks
  for; the pricing button is the outlined one.
- Two sentences that ran on double colons now read as sentences, and the
  ADR range no longer uses an en-dash.

/help
- "What does it cost?" names the numbers /pricing prints: ParaSign
  Community free forever, no card, 2 signatures per month, and ParaSign
  Pro at 49 euro a month excl. btw (59.29 incl.) with 100 signatures.
  Each half is pinned to the card it was quoted from.
- "Pay for volume, never for security" is off the support page. The fact
  behind it stays, quoted from /pricing: every plan gets the same
  encryption, the same post-quantum signatures and the same public proof
  log. A test now fails if the sales line comes back.
- "Where does my data live?" is three readable sentences instead of a
  flattened compliance table: Hetzner Nuremberg, EU law and the GDPR, no
  US provider in the data path, and email via Resend as /privacy sets
  out. Scoped to the data path per proof 1 of the messaging guide, so
  the unqualified "no US company" row from /security is not repeated
  here while that contradiction is open (guide section 9).
- The em-dash sweep on api-key-vs-totp and lost-authenticator now covers
  the H1, the tab title, the meta description and the body, not only the
  lede. A test pins all three help pages.

/developer
- The page is noindex and only reachable once signed in, so the lede
  addresses the developer reading it, not the buyer who sent them. The
  plan boundary for API access stays, and a test forbids the buyer
  phrasing coming back.

Tests: links, seo-contract, ui-truthfulness, site-claims,
frontend-loading-contract, navigation-shell, csp-inline, cache-bust,
eslint and static-sanity all pass. Every new pin was sabotaged one at a
time and went red.
Apolloccrypt added a commit that referenced this pull request Sep 2, 2026
Second pass on the review of PR #333, rebased on main (#324, #327, #328,
#331, #342).

/docs
- The buyer line is pinned to the visible paragraph. The old assertion
  matched the same words in four meta tags, so deleting the paragraph
  left the test green. Proven by sabotage, both ways.
- The buyer gets a real button to /pricing beside Quick start, same size,
  same row. Quick start stays the primary CTA the messaging guide asks
  for; the pricing button is the outlined one.
- Two sentences that ran on double colons now read as sentences, and the
  ADR range no longer uses an en-dash.

/help
- "What does it cost?" names the numbers /pricing prints: ParaSign
  Community free forever, no card, 2 signatures per month, and ParaSign
  Pro at 49 euro a month excl. btw (59.29 incl.) with 100 signatures.
  Each half is pinned to the card it was quoted from.
- "Pay for volume, never for security" is off the support page. The fact
  behind it stays, quoted from /pricing: every plan gets the same
  encryption, the same post-quantum signatures and the same public proof
  log. A test now fails if the sales line comes back.
- "Where does my data live?" is three readable sentences instead of a
  flattened compliance table: Hetzner Nuremberg, EU law and the GDPR, no
  US provider in the data path, and email via Resend as /privacy sets
  out. Scoped to the data path per proof 1 of the messaging guide, so
  the unqualified "no US company" row from /security is not repeated
  here while that contradiction is open (guide section 9).
- The em-dash sweep on api-key-vs-totp and lost-authenticator now covers
  the H1, the tab title, the meta description and the body, not only the
  lede. A test pins all three help pages.

/developer
- The page is noindex and only reachable once signed in, so the lede
  addresses the developer reading it, not the buyer who sent them. The
  plan boundary for API access stays, and a test forbids the buyer
  phrasing coming back.

Tests: links, seo-contract, ui-truthfulness, site-claims,
frontend-loading-contract, navigation-shell, csp-inline, cache-bust,
eslint and static-sanity all pass. Every new pin was sabotaged one at a
time and went red.
Apolloccrypt added a commit that referenced this pull request Sep 2, 2026
Second pass on the review of PR #333, rebased on main (#324, #327, #328,
#331, #342).

/docs
- The buyer line is pinned to the visible paragraph. The old assertion
  matched the same words in four meta tags, so deleting the paragraph
  left the test green. Proven by sabotage, both ways.
- The buyer gets a real button to /pricing beside Quick start, same size,
  same row. Quick start stays the primary CTA the messaging guide asks
  for; the pricing button is the outlined one.
- Two sentences that ran on double colons now read as sentences, and the
  ADR range no longer uses an en-dash.

/help
- "What does it cost?" names the numbers /pricing prints: ParaSign
  Community free forever, no card, 2 signatures per month, and ParaSign
  Pro at 49 euro a month excl. btw (59.29 incl.) with 100 signatures.
  Each half is pinned to the card it was quoted from.
- "Pay for volume, never for security" is off the support page. The fact
  behind it stays, quoted from /pricing: every plan gets the same
  encryption, the same post-quantum signatures and the same public proof
  log. A test now fails if the sales line comes back.
- "Where does my data live?" is three readable sentences instead of a
  flattened compliance table: Hetzner Nuremberg, EU law and the GDPR, no
  US provider in the data path, and email via Resend as /privacy sets
  out. Scoped to the data path per proof 1 of the messaging guide, so
  the unqualified "no US company" row from /security is not repeated
  here while that contradiction is open (guide section 9).
- The em-dash sweep on api-key-vs-totp and lost-authenticator now covers
  the H1, the tab title, the meta description and the body, not only the
  lede. A test pins all three help pages.

/developer
- The page is noindex and only reachable once signed in, so the lede
  addresses the developer reading it, not the buyer who sent them. The
  plan boundary for API access stays, and a test forbids the buyer
  phrasing coming back.

Tests: links, seo-contract, ui-truthfulness, site-claims,
frontend-loading-contract, navigation-shell, csp-inline, cache-bust,
eslint and static-sanity all pass. Every new pin was sabotaged one at a
time and went red.
@Apolloccrypt
Apolloccrypt deleted the chore/site-claims-tests branch September 5, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant