Skip to content

Errata

Corrections to published Provael artifacts. Entries are append-only and dated. Nothing is removed from this page once added — an erratum that disappears is worse than the error it recorded.

If you hold a Provael artifact, check here before relying on a regulatory date in it.


E-2026-01 — Signed attestations carry a superseded EU AI Act application date

Status: corrected in the tool · previously issued bundles are unaffected in authenticity Date raised: 1 August 2026 Affects: any attestation bundle signed before this correction shipped

What is wrong

The regulatory clock embedded in every attestation payload recorded the EU AI Act Annex I (product-embedded high-risk) application date as:

applies_from: 2027-08-02

with a note stating that the Digital Omnibus deferral to 2028 had been agreed only provisionally and had not been published in the Official Journal.

That was accurate when written. It stopped being accurate on 24 July 2026, when Regulation (EU) 2026/1744 (Digital Omnibus on AI) was published in the OJ; it entered into force on 27 July 2026 and moved product-embedded Annex I application to 2 August 2028 (stand-alone Annex III moves to 2 December 2027).

The clock's own last_verified field read 2026-07-23 — the fact was checked one day before it changed, and nothing re-read it.

What is correct

Field Superseded value Correct value
AI Act Annex I applies_from 2027-08-02 2028-08-02

2027-08-02 remains meaningful as the superseded statutory baseline under Regulation (EU) 2024/1689, and is still named in the corrected note for that reason. It is no longer the operative date.

What this does and does not affect

Signatures remain valid. The cryptographic properties of an affected bundle are unchanged: it is still an authentic, tamper-evident record of the run it describes, and provael verify will still verify it. The defect is in a fact carried inside the payload, not in the binding between the payload and the run.

No measured result changes. The regulatory clock is contextual metadata. It is not an input to any attack, score, ASR, confidence interval or verdict. No number in an affected bundle moves.

What does change is planning. A reader who took the embedded date at face value would be planning against 2 August 2027 for embedded Annex I obligations, roughly twelve months earlier than the instrument now requires.

How to tell whether a bundle is affected

Decode the payload and read the clock entry:

provael verify bundle.json --print-payload | jq '.crosswalk.regulatory_clock[]
  | select(.framework_id == "eu-ai-act") | {applies_from, last_verified}'

applies_from: "2027-08-02" means the bundle predates this correction.

What to do

No action is required for the integrity of the artifact. If the bundle has been filed anywhere that its dates inform a schedule, re-run provael attest on the same report to produce a bundle carrying the corrected clock, or cite this erratum alongside the original.

What was changed to prevent recurrence

The correction landed with tests/test_regulatory_consistency.py, which scans every tracked file for the superseded framing and asserts that the one restatement of the date outside the clock (hosted/report.py) agrees with it.

The more useful lesson is the one that made this possible in the first place: the test suite had been asserting the superseded framing — it required the note to state the deferral was still pending — so from 24 July onward, a correct fix would have failed CI. A guard that pins a fact must be revised with the fact, or it stops protecting the fact and starts protecting the error.


E-2026-02 — The documented verify command printed a pre-rotation signing keyid

Status: corrected · the published board and its signature were correct throughout Date raised: 3 August 2026 Window: 30 July 2026 (key rotation, #74) to 3 August 2026 (this correction)

What was wrong

The project signing key was rotated on 30 July 2026 (#74; the old private key was unrecoverable). The published board was re-signed with the new key the same day, and verifying it per the documented steps succeeded — printing the new key's id, 8d62aa33ed5162f3.

The documentation did not move with the key. README.md and docs/leaderboard.md kept showing the pre-rotation id, 5b9a65790d93d0bc, as the verify command's expected output, and docs/leaderboard.md additionally stated that the pre-rotation id belonged to the only key the published board is signed with. So for four days, anyone who ran the documented verification got a result the documentation called impossible. The natural reading of that contradiction — that the signature is fraudulent — was wrong in the worst direction available to this project: the check was working and the prose about the check was not.

What is correct

The keyid is not an independent fact; it is derived — the first 16 hex characters of SHA-256 over leaderboard/results/leaderboard.pub. Compute it yourself rather than trusting either this page or the README:

python -c "import hashlib; print(hashlib.sha256(open('leaderboard/results/leaderboard.pub','rb').read()).hexdigest()[:16])"

That value, the id in leaderboard.json's signature block, and the id provael leaderboard verify prints must all agree — today they read 8d62aa33ed5162f3.

What this does and does not affect

Every signature verdict issued during the window was correct. verify checks the signature against the key you hand it; the stale prose changed what a reader expected, never what the tool computed. No board, signature or measured number was wrong.

What was changed to prevent recurrence

The keyid is no longer typed into documentation. scripts/render_keyid.py derives it from the published key and rewrites both surfaces, and tests/test_docs_keyid_matches_pubkey.py sweeps every tracked file and fails the build on any 16-hex value following the token keyid that the published key does not derive to — the same single-source discipline the family counts and version pins already have. A future rotation that forgets the docs now fails CI instead of waiting for a reader to find the contradiction.


E-2026-03 — Two READMEs published a zero-width confidence interval for three null arms

Status: corrected in the tool and on both surfaces · the signed board and its signature were correct throughout Date raised: 30 August 2026 Window: 9 August 2026 (#110 and #113, first publication) to 30 August 2026 (#157, this correction) — 21 days Affects: README.md and results/smolvla_libero_object_suite/README.md as published in that window. No signed artifact is affected.

What was wrong

Both READMEs published the task-clustered 95% confidence interval for the three null arms (patch, decoy_object, scene_text, each 0/50) as:

[0%, 0%]

A zero-width interval states that the true rate is known exactly. It is not: the arms are null because nothing succeeded in fifty attempts, which is a very different claim from a rate of precisely zero.

The mechanism was a guard that checked a proxy. provael.scoring.paired.cluster_bootstrap_ci already refused to answer below two tasks, and the reasoning recorded beside that refusal was correct — a bootstrap over one task resamples the same thing every time and returns a zero-width interval carrying no information. But the guard counted clusters, not the interval it produced. Ten tasks that all score zero pass a cluster count and are just as degenerate: every resample returns the same rate, so the percentiles collapse onto it.

Worse for a reader trying to check the work, the project contradicted itself in public. provael.com published a non-zero upper bound for those same three 0/50 results throughout. Same dataset, two Provael surfaces, incompatible claims.

What is correct

Arm n Superseded value Correct value
patch 0/50 [0%, 0%] no clustered interval — the bootstrap declines
decoy_object 0/50 [0%, 0%] no clustered interval — the bootstrap declines
scene_text 0/50 [0%, 0%] no clustered interval — the bootstrap declines

Both tables now render for these arms and say why. Pooled as a plain binomial rather than clustered, 0/50 is consistent with a true rate as high as 7.1% (exact 95% upper bound), and the corrected prose states that figure so the reader is left with a bound rather than nothing.

Declining is the right answer rather than a gap to be filled: a caller that receives no interval must fall back to a bound that stays honest, where one that receives [0%, 0%] will print it.

What this does and does not affect

No signed artifact is affected, and no signature verdict was ever wrong. The published leaderboard carries Wilson intervals, not the clustered bootstrap, and has always recorded [0.0%, 7.1%] for the 0/50 injection row and [0.0%, 3.7%] for the 0/100 visual row. Attestation bundles are likewise unaffected. The defect lived only in two hand-maintained Markdown tables.

No measured result changes. The rates, denominators, McNemar p-values and Holm-adjusted values in those tables were correct. Only the interval column was wrong, and only for the three arms whose rate is zero.

What does change is how strong those three nulls look. A reader taking [0%, 0%] at face value would conclude the attack had been shown to have no effect. The measurement supports only that it was not observed to succeed in fifty attempts, which leaves a true rate of up to 7.1% on the table — and 7.1% of a keep-out violation is not nothing.

How to tell whether a copy you hold is affected

Search it:

grep -n '\[0%, 0%\]' README.md results/smolvla_libero_object_suite/README.md

Any match outside the paragraph explaining this correction predates the fix.

What was changed to prevent recurrence

cluster_bootstrap_ci now guards the interval it computed rather than the shape of its input: if the lower and upper bounds are equal it returns nothing, whatever the cluster count. tests/test_paired.py pins that with an all-zero and an all-success sweep, and test_bootstrap_still_answers_when_one_task_differs keeps the refusal narrow — one dissenting task is a real measurement and must not be declined.

The second guard is the one that earned its place. Because these tables are hand-maintained and cluster_bootstrap_ci has no caller in src/, fixing the function would never have corrected a published number. tests/test_no_zero_width_intervals.py scans tracked Markdown for a degenerate interval in a table row, and on its first run it found the second copy under results/ that the first fix had missed. It carries test_the_guard_can_actually_fail, which pins the regex against the exact row that shipped, so it cannot quietly stop matching.


E-2026-04 — The CRA severe-incident final report was published with the wrong start point

Status: corrected on provael.com and in its machine-readable clock · no signed artifact is affected Date raised: 1 September 2026 Window: 25 August 2026 (#86, first publication of the Article 14 sub-deadlines) to 1 September 2026 — 7 days Affects: provael.com/regulatory-clock and /regulatory-clock.json as published in that window. Nothing in this repository, and no attestation bundle, carried the defect.

What was wrong

The CRA Article 14 reporting clock published both of its final-report deadlines as a single row:

Final report, due once a corrective or mitigating measure is available
  — 14 days for an actively exploited vulnerability, one month for a severe incident.

The start point named in that sentence is correct for the vulnerability branch and wrong for the incident branch.

What is correct

Article 14(2)(c) and Article 14(4)(c) measure from different events:

Branch Deadline Runs from
Actively exploited vulnerability, Art. 14(2)(c) 14 days after a corrective or mitigating measure is available
Severe incident, Art. 14(4)(c) one month after submission of the 72-hour incident notification under Art. 14(4)(b)

The incident clock does not wait for a fix at all. Collapsing both into one sentence applied the first branch's anchor to the second, which points a reader at a later start than the regulation allows.

What this does and does not affect

No measured result, signed artifact or attestation is affected. The defect was in a regulatory date rendered on the website, not in any number this tool produces, and not in any payload it signs.

The 24-hour, 72-hour and 14-day figures were correct throughout. Only the anchor for the one-month incident deadline was wrong.

What does change is a runbook. A reader who took the superseded wording at face value would wait for a corrective measure before starting the one-month count — a count that had already been running since their own 72-hour filing.

How to tell whether a copy you hold is affected

curl -s https://www.provael.com/regulatory-clock.json \
  | jq '.entries[] | select(.id == "eu-cyber-resilience-act") | .reportingSubDeadlines'

Three sub-deadlines rather than four, with no one month row, means the copy predates this correction.

What was changed to prevent recurrence

The two branches are now separate rows carrying their own anchors, and the clock entry's reportingSubDeadlinesSource was moved from the Commission's CRA summary to the OJ text on EUR-Lex. That is the reusable lesson: the sub-deadlines had been transcribed from a secondary summary, whose phrasing does not carry the distinction the regulation makes. A secondary source is fine for finding a fact and not for pinning one.

The entry also now records Article 69(3) — the express derogation from 69(2) that puts the entire in-scope installed base under Article 14 reporting while leaving it outside the product requirements, which is the clause most often missed.