Change control and version identity
You must be able to name the exact build of the software that produced any given number, and the vendor's published verification material must match the code it actually ships. A product that cannot tell you which build ran cannot tell you whether a later correction changed your answer, and cannot tell you which of your issued reports needs withdrawing when it does.
What to ask the vendor
- Which version produced this report, and where on the report does it say so?
- Do the published fingerprints match the code I can download from you today?
- Is that match enforced automatically at release, or checked by hand?
- What is your written procedure when a calculation changes after a report is issued?
What a pass looks like
Every output names its build. An automated gate recomputes the fingerprint at release and refuses to ship when any published document disagrees. A reissue and supersession procedure exists in writing.
What a fail looks like
Version numbers typed by hand in more than one place. Public pages one release behind the product. No procedure covering reports already in a client's hands.
The release paperwork now proves itself. Every published number for the current release matches the code that shipped, the signed record verifies end to end, and an automatic check enforces six values that were wrong before. What still fails is downstream: the public website advertises the previous version, and most tools that produce client documents print no version at all.
This one is open by default as the worked exhibit. The other nine fold, and fold only. Nothing has been removed.
Evidence9 items, each checked on 16 August 2026
| # | Evidence, as recorded on 16 August 2026 |
|---|---|
| 1 | Signed chain resolves. release-evidence/v2.9.40/witness-v2.9.40.json hashes to 635f505707d8ba96e1243f651fc9f401315052f1fcbad85873c657bd48b87042; decoding the DSSE payload inside sigstore-attestation-output.txt returns subject digest sha256 635f5057..., the same value. That witness records engine sha256 72c1081d..., which is the tagged engine. |
| 2 | The attestation file is the full signed bundle, not a stub. wc -c returns 13756 bytes for v2.9.40, in line with 13383 to 27606 for the eight earlier releases and up from the 314-byte hand-written summary recorded on 12 August. Its certificate extensions carry refs/tags/v2.9.40, commit 69d96684..., and CI run 31948553931; rekor-entry.txt and the bundle both read logIndex 2488685021. |
| 3 | Published hashes match the released code and reproduce from a clean clone. git show v2.9.40:cpm-engine.js | sha256sum returns 72c1081de1e6a1b4953f4bc11fc291df5978251c666b1f5c4f891fa0999f7ec7 at 454460 bytes, equal to the packet pin; python_reference/cpm.py returns 27829ddab0a6... Cloning to a fresh folder and checking out v2.9.40 on Windows gives the same engine hash. |
| 4 | The gate checks six values, not one. tests/no-stale-version-refs.test.js compares the engine hash (line 296), the python-reference pin (line 333), the witness-to-bundle binding and bundle size (lines 351-368), the commit (line 374), the release date (line 382) and the Rekor index (line 396) against the tag and the signed bundle. CHECK_RELEASE_EVIDENCE=1 node tests/no-stale-version-refs.test.js prints PASS. |
| 5 | Cross-validation is published honestly. release-evidence/v2.9.40/VERIFY_RELEASE.md reads '925 of 989 defined comparisons executed and bit-identical, 0 failures, across 45 fixtures; 64 skipped'. node cpm-engine.crossval.js returns 'Fixtures: 45 passed, 0 failed' and 'Checks: 925 / 925 comparisons executed' with the skip caveat printed inline. node cpm-engine.test.js returns 1134 passed, 0 failed. |
| 6 | The live public site still names the previous version. Fetching the homepage returns 'Regression Tests v2.9.39' while _cpp_common/scripts/engine_version.py:26 declares ENGINE_VERSION = '2.9.40'. The same stale number sits at cpp-forensic-mcp/static/index.html:596, in two further stored copies of that page, and at try-engine.html:865. Grepping the test folder for any check on those files returns nothing. |
| 7 | Most producers carry no version. Running the seller's own producer discovery (_cpp_common/tests/test_conventions_on_page_one.py::_discover) returns 54 producers; searching each for ENGINE_VERSION, ENGINE_LABEL or engine_version matches 18 and misses 36. Separately, _cpp_common/scripts/docx_shell.py:890-906 raises an error unless the engine field carries the version from the single source of truth. |
| 8 | The pre-deploy scrub is currently red. timeout 240 python -m pytest tests/ -q in the website repo returns '1 failed, 309 passed'. The failure is tests/test_bundle_no_client_names.py, reporting a real organisation's name in a comment at _cpp_common/scripts/engine_version.py:35. git status shows that file modified and uncommitted, so nothing has shipped, but the gate does not pass as it stands. |
| 9 | Two record-keeping gaps remain. attestations/latest.json still reports version 2.9.39, commit f90b0cb and a null run URL, and git ls-files attestations/ returns only attestations/README.md, so it is not distributed. engine_version.py cites a prior bump to v2.9.36, for which git tag returns nothing, CHANGELOG.md has zero occurrences, and no release-evidence folder exists. |
What worksIncluding the parts that were wrong before and are right now
The 12 August verdict was a fail, and the reason it failed no longer holds. At that time the verification packet for the current release carried the previous release's fingerprints and told buyers no calculation behaviour had changed when the code said otherwise. That is fixed at the source, and the fix holds up under checking.
Every number in the current packet matches the tagged code. I recomputed the engine fingerprint and the companion Python fingerprint from the tag and both equal the published pins. The commit and release date match the tag. I cloned the code to a clean folder and checked out the release: the fingerprint reproduced exactly, so the instructions the packet gives a reader work on a plain Windows machine. The earlier note that they did not was a stale local copy, not a defect in the instructions.
The independent proof is real this time, and this is the largest change since the last review. The signed record is the full cryptographic bundle again, not a hand-written summary. The witness file in the packet is the byte-for-byte artifact that was signed: I hashed it and found the same digest inside the signed payload. The signing certificate names the release tag, the release commit and the build run. A buyer working offline can follow the chain from published number to signed evidence without trusting any prose.
The automatic check grew from one value to six. It recomputes the engine fingerprint, the Python fingerprint, the commit, the release date and the transparency-log entry from the tag and the signed bundle, and refuses to pass if the witness in the packet is not the signed one or if the signed record has been replaced by a summary. I ran it and it passed.
The claim of no behaviour change is true for this release. The difference between the last two releases is the version number and wording inside the product's own disclosure text.
Other gaps stay closed. There is a written procedure for reissuing and marking superseded a report already sent. Word reports refuse the "reproducible" stamp without a real version. Version fallbacks read "unknown" rather than a stale number. The web viewer's second hand-typed version is gone and a test prevents its return. Cross-validation is published as 925 of 989 comparisons executed with 64 skipped, not a clean 925 of 925, and the fitted nature of the Primavera P6 comparison is disclosed. Results reproduce: 1,134 unit checks pass, 45 fixtures pass, and all seven copies of the engine on this machine are byte-for-byte identical and correctly stamped.
What failsThe full list, not a selection
The public website still names the wrong version. The live page a prospective buyer reads advertises the previous version while the product is a release ahead. The same stale number sits in the stored copies of that page and on the interactive demo page. This is the identical defect the last grade recorded, one release later, which shows it is corrected by hand each time rather than prevented. No test reads those pages, so nothing would catch it again.
Most report producers still do not print a version. Using the seller's own list of the tools that produce client documents, 18 of 54 refer to the engine version anywhere in their code. The other 36 do not. Word documents are protected because the seal is now refused without a version, but that protection does not reach the majority of outputs, including the HTML dashboards a buyer is most likely to receive.
The pre-deploy scrub for the website software is currently failing. Its own test suite reports one failure out of 310, and the failure is the check that stops a real organisation's name reaching a shipped bundle. The offending line is a comment added today to the very file that holds the single version number. The edit is not committed, so nothing wrong has shipped, but the gate is red right now, and it is red because of a change made to the version source file. That is the shape of problem change control exists to catch. It caught it, which is the system working; leaving it red is the part that is not.
The new automatic check has real edges. It skips entirely if the release has not been tagged yet, so it is silent during the window where mistakes are made. It does not compare the build-run link in the packet to anything, though that link happens to agree with the signed certificate today.
Smaller record-keeping gaps remain. A stored summary file still describes the previous release and is not included when the code is downloaded, so anyone reading it locally gets last month's answer. The file holding the single version number still cites an earlier release that has no tag, no change-log entry and no evidence folder, so a report stamped with that version points at nothing. Two of the seven copies of the engine on disk sit outside every check by deliberate exclusion, on the reasoning that they are regenerated rather than edited.
Limits of this gradeWhat this row does not establish
This row covers version and change discipline only. It says nothing about whether the schedule calculations are correct. Knowing which code ran is not the same as the code being right.
I verified the signed record as files on disk and by internal consistency: the witness hash equals the digest inside the signed payload, and the certificate names the tag, commit and build run. I did not query the public transparency log, so I cannot independently confirm the signature was issued.
I ran no report-producing tool end to end today. The finding that 36 of 54 producers carry no version comes from reading and searching their code against the seller's own list; a tool receiving the version indirectly could be undercounted.
Everything was checked on one machine where all repositories are present. Several checks weaken or skip elsewhere. I changed nothing, committed nothing and deployed nothing.