Tenon
Evidence

Every claim on this site, and the artifact behind it.

The estate is synthetic — a fictitious Dutch estate of homes for rental and for sale, seeded through the software's own write paths. Every count below is a property of the software, not of usage. Nothing here is argued; each item states what ran, when, and what it does not cover.

Run against the standards bodies' own suites, and a register you can check without asking us.

  • OpenID4VPThe catalogue verifying 9 passed · 0 failed
  • OpenID4VCIThe issuer issuing 17 passed · 1 failed
  • EU reference issuerSD-JWT VC and ISO mdoc acquired live, verified
  • RESO CommanderRESO's own tool valid metadata · DD 1.7

Every run, its signed export, the register endpoints and the scope of each count: the evidence.

What the conformance runs are, and are not

These are the standards bodies' own test suites, self-hosted. Where a plan was only partly run, the record and this page say how much. Each run ships the suite's own signed export — every file signed by the suite — committed beside its record, so the result can be checked without taking our word for the log.

They are not a certification. Certification is a separate published status with its own process and register. What is claimed here is that the suites were run and what they returned.

OpenID4VP — the catalogue verifying

9 passed · 0 failed1 skipped2026-09-27 · 1.0.1

plan    oid4vp-1final-verifier-haip-test-plan
suite   OpenID Foundation conformance-suite
format  sd_jwt_vc      profile  haip

  verifier-happy-flow ................ PASSED
  verifier-minimal-cnf-jwk ........... PASSED
  verifier-invalid-kb-jwt-signature .. PASSED
  …
       9 PASSED · 1 skipped · 0 warning · 0 failed

Run 2026-09-27 on release 1.0.1, self-hosted and offline, every module of the plan. Every malformed presentation was correctly refused. Signed export: 10 signatures, committed with the record.

OpenID4VCI — the issuer issuing

17 passed · 1 failed0 warning · 5 skipped2026-10-04 · production

plan    oid4vci-1_0-issuer-haip-test-plan
issuer  https://tacit.software  (production)
suite   OpenID Foundation conformance-suite
format  sd_jwt_vc      profile  haip

  happy-flow ........................ PASSED
  happy-flow-additional-requests ..... PASSED
  happy-flow-multiple-clients ........ FAILED  (single-use offer, by policy)
  …
      17 PASSED · 0 warning · 5 skipped · 1 failed

Run 2026-10-04 against the production issuer, after a clause-by-clause review of HAIP 1.0 closed five deviations and cleared the three warnings the 2026-09-28 run (release 1.0.3) carried. Issuer-initiated: the wallet receives the holder's own sealed verdict from a single-use offer, and the suite validates the credential's and the status list's X.509 chains against the estate root. Signed export: 47 signatures, committed with the record. The full ceremony passes end to end — pushed authorization request with client attestation, a DPoP-bound token, holder proof of possession, and a key-bound SD-JWT VC. Disclosed: the earlier runs (2026-08-30, 2026-09-27) used a local issuer with a test certificate chain, and production issued without the chain until 1.0.3 (register R-7).

What these figures cover. The 23 OpenID4VCI module runs of the plan. The one failure is a module that redeems one offer from two wallets: the offer is single use by design, which OpenID4VCI 1.0 §4.1.1 permits. The 1.0.0 record (2026-08-30, 21 modules) stays committed as it was, annotated.

The plan's 40 FAPI 2.0 security-profile modules were run against production on 2026-10-05, the whole plan for the first time: 35 passed across all 63. Running them found nine deviations, which were fixed before the production run, among them binding the authorization code to the DPoP key and a 60-second code lifetime. Most of the remaining failures stop at the very last step by design: those modules ask for a credential without an offer, and the issuer issues a verdict only when the orchestrator asked for it. One finding is carried openly. The issuer has no consent screen at which a holder can decline, which is planned with the EU reference wallet test.

What is unrun is unrun by choice. Signed metadata, key attestation and response encryption are not required or advertised by the issuer, so the suite skips them. Each is left to the first consumer that needs it rather than built speculatively. Batch issuance is advertised and passes.

The EU reference issuer

SD-JWT VCISO mdoc2026-08-31

A credential issued by the European Commission's own reference issuer, acquired live on 2026-08-31 and verified in both formats: SD-JWT VC through the x5c chain to the reference PKI, then ISO mdoc — CBOR and COSE, holder binding, replay and relay rejection.

Two negative cases hold: a credential presented by the wrong key is refused, and an untrusted authority fails closed. This is the run that verifies the trust layer against an issuer other than our own.

RESO Commander

Valid metadataData Dictionary 1.72026-08-31

Valid metadata on Data Dictionary 1.7, validated by RESO's own tool against the live catalogue through its public edge rather than a local build. Two entity types are served, and sixty-two fields on the property resource — forty of them the standard's own, twenty-two declared local extensions. Re-run twice on 2026-08-31, after each of two deliberate breaking changes, and valid after both.

Scope, stated because the tool's name invites a wider reading: this is metadata validation. It answers whether the served schema is valid, and it does not compare declared types against the Data Dictionary. That comparison is a separate harness and has not been run.

Check it yourself

No credentialsOne dependencySelf-testing

Every listing the catalogue publishes carries the issuer's signature over its conformance verdict and over the facts the verdict was reached on — the points, the rent, the gemeente, the designations. A sample client reads the catalogue, follows the issuer named inside each signature to that issuer's own published keys, verifies the signature, and then compares every signed fact against what the catalogue served beside it.

The last step is the one that matters. A signature over a verdict alone would verify perfectly next to any rent a catalogue felt like publishing. Because the facts are bound, a catalogue that alters a listing after signing is caught — by anyone, holding no key and no account.

The client ships with a self-test, because a verifier that accepts everything verifies nothing and a reader should not have to take our word for it. The controls: the untouched attestation is accepted, a flipped signature byte is refused, an edited verdict is refused, and a rent altered after signing is caught while the signature still verifies.

On its first live run it reported one listing as refused — the issuer had signed a statement that the offer does not satisfy the governed rule. That is the mechanism working. Being able to prove a published listing unlawful is the point; a system that could only ever say yes would be worth nothing.

What the credential discloses

GET /.well-known/openid-credential-issuer

"format": "dc+sd-jwt",
"claims": [
  { "path": ["free_market_eligible"],  "sd": "allowed" },
  { "path": ["affords_rent_1750"],    "sd": "allowed" },
  { "path": ["jurisdiction"],  "sd": "never", "mandatory": true },
  …
]

Twenty-one claims in total: nineteen the holder may disclose, and two provenance claims that may never be hidden — which jurisdiction decided, and which version of the rulebook. An answer without those cannot be judged.

What a verifier learns, by tier. A presented credential is verified keylessly and its claims come back tagged with how each was proven, because that is what a criterion can demand a floor against. raw — the verifier sees the actual claim value, by SD-JWT selective disclosure. band — the verifier sees only an issuer-computed boolean, not the value behind it. zk — neither: a range proof. The zk tier is a reserved seam and is not built. Its verifier exists, checks the keyless halves it can, and then fails loudly rather than pretending.

The floor is enforced where a criterion meets a predicate, by min_privacy_tier: a criterion may demand band, and a predicate proven at raw cannot satisfy it. So the accurate general statement is not that nothing is disclosed — at raw something is — but that the interface is predicate-only on the wire, which the architecture calls ZK-shaped. ZK cryptography is the layer above that shape: the seam, not the state.

The public register

Three requests, no account and no permission. The first returns a signed head; the second proves any single entry is in the log; the third proves the log only ever grew.

GET /ledger/checkpoint
"tree_size": 1028,
"root_hash": "143adf0beb5a2907…2680b70ae1f4",
"signed_at": "2026-08-30T23:21:36Z",
"signature": "eyJhbGciOiJFUzI1NiIs…"   ES256

GET /ledger/proof?seq=1            leaf + audit path to the root

GET /ledger/consistency?first=1000&second=1028
"first_root": "1070c391…3400d8"   ← the older head still proves

Read 2026-08-30. Served by the catalogue, which holds no key: the signature is the orchestrator's, and its verification key is published separately, so the party serving the record is not the party that signed it.

The third request is the one that matters. A rewritten history can always be re-signed into a tree that looks sound on its own; what it cannot do is still produce the root an earlier head was already signed over. The log is an RFC 9162 Merkle tree — the construction that made the web's certificate authorities auditable.

No person appears in it. The record is emitted per listing, never per person: facts about a rental or a sale are published as they are, and figures derived from people are shown in ranges until at least ten stand behind them.

The counts, and what each covers

  • 15,601 testsRun by the push gate before any commit leaves a machine: the architecture and unit tiers of all three services, plus the issuer's verdict tiers against a real database. Integration, mutation and cross-service contract tiers exist and are not in this gate.
  • The A·B·C wireThe three services stand up together in continuous integration — a real issuer, a real database, and no stub of the boundary anywhere — and both the catalogue's and the orchestrator's coupled suites run against them. It is the gate that proves the contract this ecosystem exists to prove, which is the reason nothing here is permitted to fake it.
  • Every advisoryKnown vulnerabilities in what actually ships — the set the container installs, not a developer's machine — scanned weekly against the published advisory databases. On the first run, 3 September 2026, the estate carried fifty-nine; all but one closed by upgrade, and the one with no published fix is accepted with a reason a test holds to rather than a sentence. The build fails on an advisory nobody has written a disposition for, and on a disposition the scan can no longer find.
  • 169 rulesAcross 37 policies. Every rule cites the law or market convention it comes from, and the database refuses one that does not — a CHECK constraint, so whitespace does not satisfy it either.
  • 24 statutory valuesEach carrying a source URI, a publication date and the quoted text it was read from — all populated on all 24. seven further values are market convention rather than statute.
  • 32 modelsten BPMN process models and twenty-two DMN decision tables, openable in any conformant modeller.
  • 75 decision recordsIncluding the turns that were taken and then corrected. A team starting fresh pays for those twice — once to make them, once to find them.
  • 330 componentsLicence-audited across the three services from reproducible inventories. Scope is the application layer: the container base image and the vendored front-end libraries are outside it, and both are recorded as such in the inventory's own README.

Version, and what would move it

1.1.011 of 11 criteria met0 open

1.0.0 was the phase closing. That number was reserved for eleven stated exit criteria, all eleven of which are met and dated. A version that moves without a criterion moving is a version nobody can read — so it moved when the last of them did. Release 1.1.0 is a patch on that line: corrections and fixes, with no criterion reopened.

Eleven of the eleven are met and dated: the walk-throughs across all three roles, the go-live prerequisites, the public surface serving from the host on its own certificates with the development tunnel retired, the adversarial security testing run against that live deployment, the data room and the delivery of the software to the published industry standards, this site, the issuer's verdicts being emitted by the governed decision model, the bounded audit of what was delivered — the twenty-two frozen rows re-verified against the running system rather than against their own closure notes — the change freeze that audit began, the transferability position with its three claims each carrying its actual status, and the final pass over this data room.

Development is frozen, and has been since 4 September 2026. Defect fixes continue and every exit criterion is closed; new features are not added, and any found after that date are recorded as disclosures.

That matters to a reader for one reason: what you audit is what you receive. An asset still in motion has to be re-examined at closing, and this one does not. ⚠ It is also the only claim on this site that is not a property of the artifact. Everything else here can be checked by someone who did not write it; this one rests on our own discipline, and is stated so it can be held to rather than assumed.

Two threat models, and what they each found

STRIDE — securityLINDDUN — privacyGated against the code

Every security finding this system carries was found by looking at what is there. That method cannot show what was never considered, so the system was modelled and analysed twice over — once for security and once for privacy.

Both, because for this asset the privacy threats are the product. A credential rail whose point is selective disclosure, a PII-free ledger and a k-anonymity floor is not adequately described by a frame that stops at confidentiality. Two of the privacy categories name harms the security frame scores as features: an audit trail is a security control and a privacy threat at the same time, and that tension is where the design decisions actually live.

The model is derived from the running code — every way a service can reach outside itself is enumerated mechanically, not drawn — and it is checked by a test: add a new outbound path without modelling it and the build fails. That is the part most threat models lack, because prose goes stale silently and nothing notices.

What the two passes returned, on code that was already green: a trust boundary missing from the first diagram entirely — one service was modelled as a passive recipient when it is an active client of another; eight authenticated paths that recorded what happened but never who did it, the two signature paths among them; four byte-identical copies of one authorisation check; a delegated read attributed to the person who granted it rather than the service that made it; and a hand-off credential that was, in effect, a full session for the person it belonged to.

All of those are fixed and deployed. Each carries a test that fails on the unfixed code and a proof that the test can fail at all.

Then the same two frames were run systematically — every one of the 24 modelled interactions against every threat category, 312 judgements, each carrying its reason so that a negative result is evidence rather than a blank. It overturned nothing found earlier and added five more: a notification destination the caller could choose, which let one request originate from inside the network that protects the issuer; a request-size ceiling that existed on one service and had never been carried across to the other, including on the path that verifies credentials; a rate limit that was real but covered three addresses rather than the estate; an anonymous route that ran signature verification on an unbounded body; and a public page that answered with a server error, and a stack trace in its log, for any malformed identifier. All five are fixed, deployed, and verified on the running system — each with a test that fails on the unfixed code and a proof that the test can fail at all.

The last category either frame had left unexamined — whether a response reveals that a record exists — was then measured rather than reasoned about: four channels including response timing, on matched pairs of requests, with a control pair to establish the noise floor. It closed five of its six open questions and made the sixth specific.

One number worth the space. Of 312 judgements, one remains open, and it is a disclosed design decision rather than an unfinished task: two routes that a protocol requires to answer before authentication will confirm that an identifier exists. Everything else is either controlled, structurally inapplicable, a property of the deployment, or a decision recorded with its reasoning — the last of which a reader is free to disagree with, which is the point of writing them down.

What this is not. Measured against the OWASP maturity model, this is Level 1 with the Level 2 activities performed, and the distinction is deliberate rather than modest. The systematic pass is done and the artifacts are checked by tests — both Level 2 properties. What is absent is the programme around them: scheduled re-runs, and a review by someone who did not write them. An earlier draft claimed Level 2 outright and was corrected before anyone relied on it; claiming it now on the strength of the artifacts alone would repeat that error in a subtler form. There has been no third-party review — everything here is self-administered, and commissioning one is the most useful thing a buyer could do with this material.

Disclosed rather than discovered

The forward roadmap is a roadmap. Twelve capabilities are recorded as trigger-gated seams, each with the condition that would open it. Some have code behind them today; the rest are reasoned prose and nothing more. Building any of them before its trigger fires would be over-engineering, which is why they are written down rather than built — but a roadmap must never be read as an implementation.

That gate had never passed until 3 September 2026. It was enabled and it was failing, which is a different thing from being switched off, and the difference is the disclosure. Its steps stop at the first failure, so a failure in the catalogue's suite meant the orchestrator's suite never ran at all — and behind both sat a coverage floor on the only code that holds the issuer write key, which had therefore not run either. When it finally did, it found two calls to the issuer with no test behind them. Every failure was in the tests rather than in the running system, and all of them are closed; the floor is back to 100%. It is recorded because the lesson is not that it passes now: a gate that cannot run stops being a gate, and what it guards drifts quietly behind it.

The front-end libraries are outside the licence audit's count, and inside the inventories. The 330 components the seller read are the Python application layer. The 19 vendored browser libraries are enumerated in the same committed documents, each carrying its licence and the basis that licence rests on — they are outside the count, not outside the record. The container base image is in neither, and the inventory README says so rather than leaving it to be found.

The estate is synthetic. There are no real data subjects in it. That is what makes every figure above inspectable, and it is also the reason none of them is a usage claim.