Public evidence · jsonparser

123 requirements. Six defects found. Two misses published.

We ran Proof on a decade-old library we maintained ourselves, exposed the complete graph, and published the proof gaps beside the findings.

This page is the evidence dossier: the corpus, one full chain, the register, the records and the reproducers, each with the file behind it. The narrative account of how the two misses happened is the jsonparser postmortem.

01 · The dashboard

The live project dashboard.

The subject is jsonparser, a decade-old Go library maintained by Proof’s founder. It went first on purpose, as code we could not hide from. The dashboard below is jsonparser-demo, the seeded branch: it is public, it is served from the same portal a client gets, and we do not edit what it reports. The verification panel reads Needs attention because that is what the run said.

portal.reqproof.com/projects/jsonparser
Seeded product demo. The public Proof dashboard for jsonparser-demo: the latest run and its commit, a verification panel reading Needs attention, and panels for requirements, open findings, verification policy and passing checks.
SEEDED PRODUCT DEMO jsonparser-demo · branch proof-demo on the probelabs/jsonparser fork. The 10 open findings in this shot are seeded entries, and none of them is a defect in the library. Read the labels first.

123 requirements28 approved4 issue records, every one fixed2 misses published

The audit ran in July 2026. All four counts are read off master in buger/jsonparser and were last counted on 30 August 2026, which is why they differ from the seeded branch in the shot above. Each count is a directory or a document you can count yourself; the accounting strip names every population and section 04 reconciles the defect numbers against the files.

Overview
The latest run, the commit it ran on, and whether verification is complete.
Findings
The issue catalogue: known issues, problem reports, accepted risks and failing checks, by kind.
Requirements
Every requirement with its specification, rationale, obligations, evidence and trace links.
Audit checks
The checks behind the gate, and which of them are passing on this commit.
Map
The intent graph: requirements, code, tests and hazards, and the edges between them.
Timeline and activity
What changed, when, and which evidence had to run again because of it.

02 · The corpus

What Proof built.

The behavior of the library was formalized into 123 requirements: 7 stakeholder promises and 116 system clauses, plain YAML in the tree. A person who owns the code approves each sentence, and only an approved requirement is judged against. On 30 August 2026, 28 carried status: approved and the other 95 were still candidates at status: review. Each file says which.

portal.reqproof.com/projects/jsonparser/requirements
Seeded product demo. The Requirements Explorer of the Proof portal for jsonparser-demo. Panels across the top read: Requirements 123, broken down as STK 7, SYS 116, SW 0 and INT 0; meets verification policy 123 of 123; with tests 116 of 123; with docs not reconstructable for a historical run; coverage gaps 0. The table below lists requirements with their component, level, status, assurance level, formalization, coverage and finding count. The first row is a stakeholder requirement at status review; the system rows beneath it read approved.
SEEDED PRODUCT DEMO jsonparser-demo · the same run as the dashboard above. The corpus it lists, counted by the product itself: 123 requirements, of which 7 are stakeholder and 116 are system, with no software level in this project. The status column is mixed on purpose: the top stakeholder row reads review, the state section 03 records for STK-REQ-005.
Requirements
7 stakeholder promises and 116 system clauses. Every one carries its own status field, and only an approved requirement is judged against.
Formal specifications
The system clauses carry machine-checkable specifications: FRETish, compiled to temporal logic.
Annotations
All 279 functions declare the requirements they serve, in the source itself, so the trace moves when the code moves.
Obligations
Each requirement lists what must hold for it to count as verified, with its worst case beside it.
Evidence manifests
The runs that satisfy each obligation, in proof/evidence/. An unmet obligation shows up as a hole in the file.
Condition coverage
MC/DC measured at 100% of decisions and 100% of conditions on the audited code. Section 05 is where that number stops being enough.
The gate
The strict audit gate on every commit: zero errors, zero warnings. It reruns without us.

03 · One chain

One requirement, end to end.

One requirement, and every artifact attached to it. Each step below is a file in the public repository. Open them in order and you have checked the chain yourself, without asking us anything.

The finding, walked in time

01requirement

SYS-REQ-009approved

Set() replaces the addressed value, creates a type-consistent path, or returns KeyPathNotFoundError. A person who owns the code approved that sentence before any code was judged against it.

specs/system/requirements/ · SYS-REQ-009.req.yaml

02known issue

KI-3was violated

An array-index path component under an object parent produced malformed JSON output, and Set() returned it with a nil error. The caller had no signal that the bytes could not be parsed again.

proof/known-issues/ · KI-3.yaml

03reproducer

set_spec_test.goasserted the bug

One executable test, pinning the failure while it was live: it asserted that the output was not valid JSON. The file carries // Verifies: SYS-REQ-009 and // Reproduces: KI-3 on one screen, so it says which promise it proves and which failure it pins.

set_spec_test.go · TestSetArrayIndexUnderObjectMalformedJSON_KI3

04affected revision

parser.gointroduced_in: inceptionviolated here

The record names the code that carried it and dates it to the library's first release, so every revision before the fix is affected. The requirement's own history stamps the window: verification failing on 26 July 2026, passing again the next morning.

parser.go · createInsertComponent

05fix

DEFECT-260726-MFPAon master

Set() now replaces a mismatched container with the type the path expects, so every non-erroring call returns valid JSON. The defect record names the root cause, the obligation it added, and the regression test.

proof/problem-reports/ · DEFECT-260726-MFPA.yaml

06new evidence

set_spec_test.gopassing

The same test as step 03, still in the suite, flipped at the fix: it now asserts that every cross-type call returns valid JSON. The obligations on the requirement run against it again.

proof/ · evidence/ki3-set-array-index-under-object.yaml

07re-approved requirement

SYS-REQ-009verified

The same sentence as step 01, re-approved by the engineer who owns the code once the hardening was attached, with the defect named in the record as the reason for the re-approval.

specs/system/requirements/ · SYS-REQ-009.req.yaml, verification block

Fig. 01 · PUBLIC AUDIT EVIDENCE Seven steps, seven records, and each one is a file in the public repository on master. The requirement that opens the chain is the requirement that closes it.

The same chain, file by file

  1. 01

    Stakeholder promiseSTK-REQ-005status: review

    Users update or delete addressed JSON values through the experimental mutation helpers, with deterministic edge-case behavior.

    Read the file and the third line says status: review. The promise at the top of this chain is still a candidate, so nothing is judged against it yet, and we left it in view. The approved sentence is the next step, and that is where the obligations and the evidence attach.

    specs/stakeholder/requirements/STK-REQ-005.req.yaml

  2. 02

    RequirementSYS-REQ-009status: approved

    Set must update the addressed JSON value without corrupting unrelated structure.

    Approved means a person who owns the code approved the requirement. Until a requirement is approved it is a candidate, and nothing is judged against it. The line above is our summary of it; the approved text is the description field in SYS-REQ-009.req.yaml, and it is longer and more exact than a card can carry. The file names the reviewer and the role, and it records that it satisfies the promise in step 01.

    specs/system/requirements/SYS-REQ-009.req.yaml

  3. 03

    Obligations4

    Boundary, idempotency, malformed input, nested mutation. What must hold for the requirement to count as verified, and which evidence proves each one.

    The obligations are where a requirement stops being prose. Each one is listed in the requirement file next to its worst case, and the manifests that satisfy it live beside them, so an unmet obligation shows up as a hole in the file.

    SYS-REQ-009.req.yaml · proof/evidence/

  4. 04

    Implemented byparser.go:1394

    The comment directly above func Set lists every requirement that function carries, SYS-REQ-009 first.

    The annotation lives in the source, so it moves when the code moves. Follow the link and the highlighted lines are the annotation and the function it sits on.

    parser.go#L1394-L1395

  5. 05

    Verified byset_spec_test.go

    One test file carries // Verifies: SYS-REQ-009 and // Reproduces: KI-3 (fixed), on one screen.

    This is the join that makes the rest checkable: the same file says which promise it proves and which failure it pins. Run it with go test -run TestSetArrayIndexUnderObjectMalformedJSON_KI3.

    set_spec_test.go

  6. 06

    Known issueKI-3was violated

    The register entry: severity with its basis, the evidence, and the review dates.

    A known issue is an unresolved problem. It stays a known issue until the closure is verified, and the register says which state it is in. This one now reads status: fixed.

    proof/known-issues/KI-3.yaml

  7. 07

    Defect recordDEFECT-260726-MFPAcovered by requirement

    The history that stays after the fix: root cause named, hardening recorded, reproducer pinned in CI.

    The requirement that opened the chain is the requirement that closes it, proven again on the fixed code. This record strengthens SYS-REQ-009, which is step 02.

    proof/problem-reports/DEFECT-260726-MFPA.yaml

Nothing in that chain is a screenshot of a screen that does not exist. Every step is a file in a public repository, and the links above go to the files themselves.

04 · The findings

Six defects the review found.

The count is worth stating carefully, because the artifacts are public and you can count them yourself. proof/problem-reports/ on master holds eight defect files, every one fixed. Two of the eight were reported by outside contributors and credited by name in the changelog, which leaves six the review found itself. Those two outside reports are the two misses in section 05.

No reproducer, no finding.

A finding you cannot re-run is an opinion with formatting. The reproducer is what gives a finding its authority. Every entry below is dated, publishes its own triage boundary, and names the person who validated it or says why the name is withheld.

The register, read off master

6

found by the review

2

reported outside, credited

8

defect files on master

4

known-issue records shown in this register

Fig. 02 · PUBLIC AUDIT EVIDENCE The register lists known issues; the full defect history sits beside them in proof/problem-reports/, and the two outside reports are credited by name in the CHANGELOG — they are the two misses in section 05. KI-2 and KI-4 were fixed later, in v1.6.0.
Finding What was wrong Result
KI-1

An empty key path component panicked in the key search where an error was owed.

KI-1.yaml

Fixed
KI-2

ParseInt("-") returned zero with no error, a silent false success on sign-only input.

KI-2.yaml

Fixed
KI-3

Set() with an array-index path component under an object parent produced malformed JSON output.receipt above

KI-3.yaml

Fixed
KI-4

Set() on a top-level array index beyond the length returned a not-found error where an append was owed.

KI-4.yaml

Fixed

Four issue records on the project's master branch, every one of them status: fixed, every one of them carrying the command that reproduces it. Four records and eight defect files count different things, and the register is the smaller of the two because a defect found and fixed in one pass never becomes a known issue: DEFECT-260726-3PSJ, DEFECT-260727-ARR1 and DEFECT-260727-SNGT name none. KI-1's only defect file is DEFECT-260726-QS2V, which is one of the two credited outside rather than one of the six. The wider defect history sits beside them in proof/problem-reports/.

One resolved defect record, in full.

A resolved defect record is what stays after a fix is verified. This is DEFECT-260726-MFPA, the record for KI-3, read off the public register.

DEFECT-260726-MFPA · KI-3 · Set() / array-index path under an object parent Fixed

Expected

Set writes the value at the addressed path.

Observed

Malformed JSON output on array-index paths under object parents.

Reproducer

set_spec_test.go · // Reproduces: KI-3 (fixed) · go test -run TestSetArrayIndexUnderObjectMalformedJSON_KI3

While the bug was live this test asserted the bug: it pinned the output as not valid JSON. It was flipped at the fix and now asserts the opposite, so a regression trips it either way. It was never red and then green, and saying so would have been the easier story.

Severity

high · basis: reproducer. Our assessment, weighted by reachability and impact, offered as a starting point for the maintainers' triage. No CVSS determination is claimed.

Retest

Fixed and pinned. The reproducer stays in CI as a regression test.

Originating issue
Where the problem came in, including when a user reported it.
Defect record
The evidence-bearing history after the fix is verified: root cause named, hardening recorded.
Closure evidence
The test that pins the failure, and the named revision it was run on. Some pin it by failing until the fix lands. Some pin it by asserting the broken behavior while it is live and getting flipped at the fix. The record says which.
Class-closure status
Whether the whole failure mode is closed, or only this instance of it. The two are never merged into one count.
Fixing change
The change that landed upstream, and the release that carries it.
Affected requirements
Which promises this touched, so the evidence they depend on runs again.

One reproducer settles the instance. Closing the whole defect class takes broader evidence, and the record states which of the two it claims.

05 · The misses

Two defects the gate missed.

The postmortem on this project is called Two Bugs That Escaped L3 Strict Proof Review, and two is the honest number. Both misses are in Set(). The affected code carried 100% MC/DC at the time, which means every condition in the decision logic we checked was exercised. It does not mean the behavior was correct.

docs/proof-gap-root-cause.mdon masterdated 2026-07-26author: proof-gap review

What escaped, one of two

Ask Set to write 9 at index [5] of the array [1,2], and it hands you back [9]. No panic. Valid JSON. The rest of the data, gone.

DEFECT-260727-WWWY.yaml

What escaped, two of two

Ask Set to write at the path "a" then "", and it panics. The empty path component is the same failure class a hazard sweep had already closed. The sweep found seven sites, fixed all seven and reported clean. The eighth was written keys[depth:][0][0]; the pattern the sweep matched on was keys[i][0], and it walked past.

This escape has no problem report of its own on master. What it has is the record of the sweep that missed it: DEFECT-260726-QS2V closes the other seven sites and signs off sibling_sweep: result: clean — “No further live instances of the unguarded-index anti-pattern remain in parser.go.” The eighth site falsified that sentence on the same branch that wrote it. Read the two together: the sweep record first, then section 3 of the postmortem, which is the only account of the site the sweep walked past.

the sweep record DEFECT-260726-QS2V.yaml · the escape itself proof-gap-root-cause.md §3

What the record says

Both problem reports open with source: type: audit_finding. The project's changelog credits an outside reporter for each of the same two fixes, by name, and lists both names again in that release's acknowledgments. Two files in one repository disagree about where these came in, and the public record does not settle it. What is not in tension: the gate stopped neither defect, and a credit like that stands.

Our own ledger promises to record when a user reported a problem, and on these two entries it does not. That is a third gap, and we state it here.

CHANGELOG.md

Why they count as misses

Both defects date to the library's inception, years before this work, so writing them was not the failure. Certifying around them was. Both sat inside the declared scope with evidence attached, so nothing about the boundary excuses either one.

The changelog names three more escapes, fixed in v1.3.1 with no postmortem of their own. That is a gap in the register rather than an absence of misses, and it is recorded here for the same reason the other two are.

What we published

The root-cause analysis, blameless, specific and public, on the project's master branch, next to the finding and the fix, where you can re-run both. It runs to six sections including a remediation table, and the last of them is titled What proof did right, because the specified hazards were caught precisely for being specified. It is not signed. Its author line reads proof-gap review, and what you check it against is the reproducers.

An auditor that only shows you its wins is asking for faith. Open the postmortem and its own first line will tell you the count is two. The narrative account of both escapes walks how each one got through.

06 · The hardening

How the misses changed the graph.

Two requirements that did not exist before, both now approved, and three instruments that check them. Every row below is a file on master, and every one of them runs on each commit.

SYS-REQ-110
What Set owes an array index past the end of the array, for every element type, including the ones the old code skipped. SYS-REQ-110.req.yaml
SYS-REQ-111
The empty path component is a defined input on every entry point, and it must never panic. SYS-REQ-111.req.yaml
Reference oracle
Compares output against the standard library, so a plausible-looking wrong answer still fails. reference_oracle_test.go
Path fuzzer
Explores the key path as well as the bytes, which is where both escapes lived. path_fuzz_test.go
Signal rule
Flags the unguarded dereference in every spelling, including the one the sweep walked past. Its file header says so. unchecked-caller-slice-deref.yaml

A requirement with nothing checking it is a sentence, which is why each of the two arrived with machinery attached. One passing reproducer proves an instance dead; closing a whole class takes broader evidence, and each record states which of the two it claims.

07 · The receipts

Rerun everything yourself.

Everything above resolves to a file in a public repository. Here are the six you can open: the repository, the requirements, the live graph, one reproducer, the register and the postmortem. Five are files on master under the project's own licence. The sixth is the seeded demo, badged as such below.

  • The repository ↗

    buger/jsonparser. The specs, the proof artifacts and the gate all live in the tree.

  • The requirements ↗

    specs/ — every requirement as plain YAML, each one carrying its own status field. Read one and judge whether it says anything.

  • The live graph ↗

    portal.reqproof.com — the dashboard, the findings, the requirements and the graph. No account, no sales step. Its open entries are seeded, and none of them is a defect in the library. Read the labels first.

  • A reproducer ↗

    set_spec_test.go — what it verifies and what it reproduces, on one screen. Clone the repository and run go test -run TestSetArrayIndexUnderObjectMalformedJSON_KI3.

  • The issue register ↗

    proof/known-issues/ — each known issue with its severity basis, its evidence and its review dates.

  • The postmortem ↗

    proof-gap-root-cause.md — the two defects that got past us, and the gaps in our own proof that let them through.

08 · On your code

Install the same system on your code.

This is the audit we sell, run in public. The Continuous Correctness Audit puts the same loop on one component of yours: requirements your engineers approve, the code held to them, the evidence in your repository, re-run on every release. Yours stays private, and the trust page explains why that is structural.