Draw date: 2026-08-21. Tool: ctdl-validate at the commit this file
landed on, with the vendored CTDL/CTDL-ASN snapshot retrieved 2026-08-06.
Harness: tools/registry_survey.py.
Evidence: 2026-08-21-registry-survey-at-scale.json.
The 2026-08-15 survey validated 120 published documents and found that every ERROR it raised traced to CTDL's own schema encoding. That finding changed the tool. This run asks the next question at ten times the scale, with the false-positive class removed: what does the validator say about the published corpus now, and how much of what it says is an answer rather than "I cannot see the document this points at"?
Everything in this section was written and committed before the first page was fetched. The results sections below were written afterwards and do not alter it.
Frame. GET https://credentialengineregistry.org/ce-registry/envelopes?page=N&per_page=1,
for N in 1..X-Total, where X-Total is the response header read at draw
time. One item per page makes page N the Nth envelope, so a uniform draw of
page numbers is a uniform draw of envelopes. The endpoint ignores
resource_type and envelope_ctdl_type query parameters — the same X-Total
comes back with or without them, checked 2026-08-21 — so there is no keyless
per-type frame and therefore no stratified draw. The per-type breakdown below
is reported from each envelope's own envelope_ctdl_type label after the
fact, and a type with fewer than 20 documents in the sample is listed but not
interpreted.
Draw. 1,200 page numbers: random.Random(20260821).sample(range(1, X-Total + 1), 1200),
sorted. The seed is the date. The evidence file records X-Total, the seed,
and every page number, so the draw is recomputable without the network, and
tests/test_findings_evidence.py recomputes it. (Amended after the draw,
2026-08-21: the tests for this run were given their own module,
tests/test_findings_registry_at_scale.py, to keep the 2026-08-15 tests
untouched. Nothing else in this section has changed since it was committed.)
Every drawn page is published. Each of the 1,200 appears in the evidence
exactly once: as a document row, or as an exclusion with one of six reasons
fixed in advance in the harness (EXCLUSION_REASONS): fetch-failed,
empty-page, not-resource-data, no-document, duplicate-ctid,
unreadable. No retries. A page that fails is a row that says so.
Two passes. Every document is validated alone, as a publisher would run
the tool, and then with --resolve supplied every document its references
name. Resolution is one hop and follows only references into
credentialengineregistry.org/resources/; a reference to a credreg.net
vocabulary term, to some other path on the Registry host, or to anybody's own
website is not fetched and is counted by kind, never by name. There is no cap
on the number of neighbours fetched. A neighbour the validator cannot read is
recorded by CTID and reason and left out of the supplied set rather than
allowed to abort the pass.
What is recorded. Per document: the page number, the CTID, the Registry's
type label, an opaque publisher label (P001, P002, … in order of first
appearance, so concentration can be measured without recording identity), the
entity count, the declared classes, finding codes with counts and one example
location per code for each pass, the Registry resources it referenced, and the
kinds of reference left unsettled. Every recorded example value passes an
allow-list — a JSON pointer, a CTID, a blank node id, a Registry or credreg.net
IRI, a prefixed term — and anything else is replaced by its length. No name,
description, price, address, contact detail, or third-party URL. The fetched
documents are cached locally and are not committed.
What will be published, whatever it shows. Headline counts for both
passes; per-code and per-severity totals; the per-type table; publisher
concentration; every document carrying an ERROR, named by CTID and JSON
pointer; every exclusion, named by page and reason; the unsettled residue by
kind; and the change against the 120-document run, which is comparable because
that run's cache has been re-validated by the same tool version
(2026-08-15-published-registry-survey.revalidated-2026-08-21.json).
Every number in those tables is recomputed from the evidence JSON by
tests/test_findings_evidence.py (see the amendment above: the module is
tests/test_findings_registry_at_scale.py).
Hand verification. Every ERROR in the resolved pass is checked against the cached source bytes of the document and, where the finding depends on it, of the neighbour, before it is published. A finding that does not hold against the bytes is reported as a defect in this tool, not in the document.
Politeness. One process, one request at a time, a 2-second minimum
interval, the ctdl-validate product token and a link to this repository in
the User-Agent, robots.txt checked before the first request and at every
redirect, no bulk endpoint, no API key. Nothing is filed with anyone on the
strength of this run.
Conformance. A document with no findings has passed this tool's five structural checks and nothing else. It has not been shown to conform to CTDL, to the Registry's Minimum Data Policy, or to anything this tool does not check. No percentage here is a population estimate of anything but the documents the tool read.
X-Total was 395,847 envelopes when the draw was made, so the sample is
1,200 of 395,847 — a little under a third of one percent. robots.txt answered
HTTP 404 again at run time, which RFC 9309 section 2.3.1.3 treats as permission
to proceed, and that outcome is recorded in the evidence rather than assumed
from the earlier check.
Nothing was lost. All 1,200 drawn pages were fetched, none failed, and
none were excluded — every one carried a resource_data envelope with a
CTID and a decoded_resource, no two drew the same CTID, and the validator
read all 1,200. The six exclusion reasons fixed in advance went unused.
The resolve hop. The 1,200 documents referenced 1,660 distinct
credentialengineregistry.org/resources/ URIs. 1,659 were fetched and
one answered HTTP 404, leaving zero referenced documents neither
fetched nor accounted for. All 1,659 parsed, so none was rejected from the
supplied set; the second pass had 1,659 documents and 1,659 entities in hand.
Requests, and a counter that was wrong. The cache the analysis reads cannot
have been built by fewer than 2,861 requests: 1,200 envelope pages, 1,659
neighbour documents, the one neighbour that answered 404, and one probe for
X-Total. The harness's own counter recorded 1,770, and that discrepancy
is the more useful finding. The draw was interrupted partway through and
resumed; the counter was banked only at the end of a phase, so the interrupted
run carried its whole tally out with it. A count that a resume can lose is not
a measurement. The harness now banks the tally after every request, and the
evidence file publishes both numbers side by side — access.requests.recorded
and access.requests.implied_by_the_cache — rather than the one that reads
better. Every number elsewhere in this write-up is derived from the cache and
the sample, not from that counter.
The resumed run needed no further requests: the cache was already complete, so
the pass that produced this evidence file made zero. The 2-second minimum
interval was never shortened, no Crawl-delay was published, and nothing in
the run was rate-limited — the single 404 is a document that is not there, not
a refusal.
Both passes read the same 1,200 documents. 94 of them (8%) produced no
finding of any kind, and 28 (2%) carry at least one ERROR. The difference
--resolve makes is the whole right-hand column: supplying the 1,659
neighbours converted 3,171 of 3,326 UNVERIFIABLE findings (95%) into real
verdicts, and left 155. Document by document, 1,106 of the 1,200 had at least
one reference the tool could not see; with the neighbours in hand, 33 did.
It converted them into agreement. Supplying 1,659 other people's documents produced no new ERROR at all — the ERROR count is identical in both columns — because every ERROR in this corpus is either a domain violation, which needs no neighbour to judge, or a blank-node reference, which no neighbour can ever settle. That is the outcome ADR 0004 predicted and did not guarantee: resolution settled unknowns without manufacturing failures.
| Measure | Alone | With --resolve |
|---|---|---|
| Documents validated | 1,200 | 1,200 |
| Documents with no findings | 94 | 94 |
| Documents with at least one ERROR | 28 | 28 |
| ERROR findings | 159 | 159 |
| WARNING findings | 42 | 42 |
| INFO findings | 385 | 3,598 |
| UNVERIFIABLE findings | 3,326 | 155 |
By finding code:
| Code | Severity | Alone | With --resolve |
|---|---|---|---|
CONCEPT_RANGE_CONFLICT |
INFO | 335 | 335 |
CTID_NOT_UUIDV4 |
WARNING | 42 | 42 |
DOMAIN_VIOLATION |
ERROR | 137 | 137 |
INVERSE_ONE_DIRECTION |
INFO | 31 | 31 |
REF_OUTSIDE_PAYLOAD |
UNVERIFIABLE | 3,326 | 155 |
REF_RESOLVED_SUPPLIED |
INFO | 0 | 3,171 |
REF_UNRESOLVED_BNODE |
ERROR | 22 | 22 |
VERSION_RANGE_CONFLICT |
INFO | 19 | 61 |
RANGE_VIOLATION is absent from both columns. Across 1,200 published
documents and 1,659 supplied neighbours, this tool found no reference whose
target was of a class the property's declared range excludes that survived
hand-checking — see the ERROR section, where 108 of them did not.
The draw is uniform over envelopes, not over types, so this table describes
what the corpus is made of rather than what any type is like in general.
Forty-four distinct envelope_ctdl_type labels appeared; the sixteen with at
least 20 documents are interpreted, and the rest are pooled. Columns are the
resolved pass.
| Registry type | Documents | No findings | With an ERROR | UNVERIFIABLE left |
|---|---|---|---|---|
ceterms:Task |
150 | 0 | 0 | 0 |
ceterms:Course |
128 | 0 | 5 | 0 |
ceterms:Certificate |
121 | 0 | 0 | 0 |
ceterms:CredentialOrganization |
107 | 94 | 0 | 0 |
ceterms:LearningProgram |
79 | 0 | 0 | 0 |
ceterms:TransferValueProfile |
74 | 0 | 22 | 0 |
ceterms:LearningOpportunityProfile |
63 | 0 | 1 | 0 |
qdata:DataSetProfile |
63 | 0 | 0 | 0 |
ceterms:AssociateDegree |
61 | 0 | 0 | 0 |
ceterms:BachelorDegree |
59 | 0 | 0 | 0 |
ceasn:CompetencyFramework |
35 | 0 | 0 | 149 |
ceterms:MasterDegree |
29 | 0 | 0 | 0 |
ceterms:Certification |
28 | 0 | 0 | 0 |
ceterms:License |
28 | 0 | 0 | 1 |
ceterms:CertificateOfCompletion |
27 | 0 | 0 | 0 |
ceterms:AccreditAction |
26 | 0 | 0 | 0 |
| all other types (28) | 122 | 0 | 0 | 5 |
Two columns are almost entirely one row each. Every one of the 94 documents
with no findings is a ceterms:CredentialOrganization — 94 of the 107 drawn.
Organizations describe themselves with literals and reference little, so there
is less for a structural checker to have an opinion about; this is a fact about
what the checks can reach, not a quality ranking. Likewise 149 of the 155
references still unsettled after --resolve belong to the 35 competency
frameworks, which point at credreg.net vocabulary terms and at alignment
targets on other people's websites, neither of which this hop follows.
The protocol committed to checking every ERROR against the cached source bytes before publishing it, and to reporting any that did not hold as a defect in this tool. That is what happened. The resolved pass first raised 267 ERROR findings across 39 documents. Hand-checking each one against the bytes and against the schema declaration it cites found that 108 of them, in 11 documents, were this tool's fault, in two classes:
- 47 findings, one document.
ceterms:hasMemberdeclares exactly one range term,rdfs:Resource. RDF Schema 1.1 section 3.1 defines that as "the class of everything", so the declaration excludes nothing — but no CTDL class reachesrdfs:Resourcebyrdfs:subClassOf, so matching a target's classes against it rejected every entity instead of accepting every entity. One collection listing 47 licences was reported as 47 range violations. The same inversion applied toceterms:isSimilarToandowl:sameAs. - 61 findings, 32 documents.
ceterms:latestVersion,ceterms:nextVersionandceterms:previousVersioneach declare aschema:rangeIncludesthat is a strict subset of their ownschema:domainIncludes, dropping the same six classes from all three:ceasn:Competency,ceasn:CompetencyFramework,ceasn:Rubric,ceterms:Collection,ceterms:Pathwayandceterms:TransferValueProfile. So CTDL says aTransferValueProfilemay have a previous version while saying that version may not be aTransferValueProfile. Thirty-two transfer value profiles versioned themselves the only way the vocabulary leaves open, and were told they were wrong.
Both are now dispositions rather than errors: the first raises nothing at all,
the second raises VERSION_RANGE_CONFLICT at INFO, which is where the 61
findings in the code table come from. Both fixes are narrow, both are tested
against the snapshot they rest on, and both tests fail if Credential Engine
changes the declaration — at which point the disposition should be revisited
rather than silently kept.
The second fix overturns a decision this project made deliberately, and the
grounds matter. The 2026-08-15 survey found the same version-property
asymmetry in a single document, wrote it down as conflict 5, and chose to keep
raising an ERROR on the stated grounds that "one publisher's usage is not
enough to overturn a declaration". Scaling up did not answer that
objection. All 32 documents are from one publisher — the same one — and of
the 74 transfer value profiles drawn, only that publisher's 32 use a version
property at all. In the whole corpus of 2,859 documents fetched for this run,
exactly two publishers use any of the three version properties. So the
frequency argument that carries CONCEPT_RANGE_CONFLICT, where the Registry's
dominant encoding contradicts the declaration, is not available here and
is not being made.
What changed is the argument, not the count. The declaration is incoherent on
its own terms: the range of all three properties is a strict subset of their
own domain, and for the six dropped classes the two halves cannot both be
honoured in any way that means anything. A TransferValueProfile may have a
previous version, says the domain; that version may be any of 55 classes, none
of which is TransferValueProfile, says the range. Every option the range
leaves open makes a transfer value profile's earlier version a credential.
Either the domain wrongly admits these six classes or the range wrongly
excludes them — one of the two is wrong regardless of what anybody publishes,
and a document that picked the only usable reading is not the thing to report.
An ERROR means "fix this before publishing", and there is nothing here to fix
it to. That is why it is INFO and why the message says which decision the tool
is declining to make rather than pretending the question is settled.
That leaves 159 ERROR findings in 28 documents, each checked against the bytes and each standing. The count column is per document and per code; the property and pointer name one example of that code in that document.
| Document (CTID) | Code | Example property | Example location | Findings |
|---|---|---|---|---|
ce-112e13c8-91ed-4e1f-b189-d112161270be |
DOMAIN_VIOLATION |
ceterms:framework |
$.@graph[0].ceterms:teaches[0] |
44 |
ce-42dbf192-24a2-497c-9e7c-ed88e837572d |
DOMAIN_VIOLATION |
ceterms:framework |
$.@graph[0].ceterms:teaches[0] |
32 |
ce-45d1f065-f65b-4372-8b78-d0565c86e74d |
DOMAIN_VIOLATION |
ceterms:framework |
$.@graph[0].ceterms:teaches[0] |
28 |
ce-472cac79-4599-4261-a40a-7b9d2d7d8965 |
DOMAIN_VIOLATION |
ceterms:framework |
$.@graph[0].ceterms:teaches[0] |
20 |
ce-f2bf1ed1-0f8d-46d8-90c5-3827841923e8 |
DOMAIN_VIOLATION |
ceterms:framework |
$.@graph[0].ceterms:teaches[0] |
12 |
ce-58c1723d-01d1-4636-90de-08c97f272320 |
DOMAIN_VIOLATION |
qdata:relevantDataSet |
$.@graph[0].ceterms:aggregateData[0] |
1 |
ce-02c1f6af-cf0f-4a39-9138-c2a6c4a48339 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:dad498af-fb57-03a3-c89f-d3c9d3c1031d |
1 |
ce-03155d72-17e2-4dc5-a809-6b388e1a81c3 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:8d3fa54d-eec3-413d-cc02-b37a725ac5c7 |
1 |
ce-0b032255-5f55-40f4-bb1c-9790a97ece7b |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:f3660390-195b-0173-9b02-36462dab7864 |
1 |
ce-195526f5-904b-437a-9966-008154148d49 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:3d90c57c-e986-5cbe-6b5c-4217ce9fe00f |
1 |
ce-2e4f495c-7ee0-49b4-b526-adf329b81612 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:70a5fec9-1dd7-4ac6-0c59-8a135bac600b |
1 |
ce-340cba36-8672-41b6-a279-5620d6295d6c |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:7df40def-4eff-6538-1c54-61fb1f156af1 |
1 |
ce-3fe27180-3928-4ce2-9a09-c5fccde090e1 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:14c742b0-f927-5ebc-7c5a-4da10296cfa0 |
1 |
ce-46ba1356-597e-4abc-86d4-dc511f0795cc |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:d7b1cf60-1ac8-d0c8-779d-1a16eec83d3a |
1 |
ce-47602f69-b340-4714-92a6-c03c8e9075bc |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:d82225af-a2c7-462b-d3f0-88c7fcae1226 |
1 |
ce-500e1760-e561-48fc-8299-9df04a1a5c97 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:52f327cd-80f3-e8a6-7ad2-dd5698be6201 |
1 |
ce-6c33ff48-c9c9-4f19-b32e-d3890cfc9527 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:beb1678f-6c4d-a46a-9b03-05ea04d15b5d |
1 |
ce-6e2b7809-ab45-4263-94cc-52db3651d74d |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:5f65f04a-4764-4010-ddf8-cdcd49d6bfa4 |
1 |
ce-87f081b4-efee-4369-a6d4-e69f6769c520 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:3e0ddcb7-2b53-5840-03cb-bea6c80bd69e |
1 |
ce-ab264e86-8188-4d54-bb7b-6477f2f36fdd |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:fcffdd1f-cc30-cd4b-5a9a-0b813fa3213d |
1 |
ce-b0083555-5e0a-424f-9c28-2240221592ad |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:26822d7b-29f7-7118-90f9-d5860894213f |
1 |
ce-c3570a3b-fa79-4b25-bc1a-37c7e09df436 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:43bdaef5-bf72-72b7-ff38-eae807c0aec0 |
1 |
ce-c73b88ac-d683-4784-bf9a-0c1e8d93374d |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:e0266955-6a71-34c1-8a02-ce85ed310a43 |
1 |
ce-cfdea799-3feb-46f7-8233-e0aed115f2c0 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:943b1689-23cf-773b-e86d-7359d8c749c3 |
1 |
ce-d6feb462-f5f7-48e2-a0b8-6b9f99ae1bca |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:a4d8b414-6baf-f7e7-db20-40874613cbbb |
1 |
ce-e3796db1-b190-4ace-ade1-a975134e07f1 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:2ec10c76-a2ec-2002-fba7-a27f5f23fe52 |
1 |
ce-eba6705b-9ef4-422e-8368-9c5b65a03705 |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:f23dea3f-f538-8c6f-465c-f365e89ee069 |
1 |
ce-f5af1b64-696e-4db9-85d7-e5c0fea9d77b |
REF_UNRESOLVED_BNODE |
ceterms:ownedBy |
_:4e7fa578-5462-6ee1-f9bc-fdbed21467fa |
1 |
They are three shapes, and each was checked a different way.
136 findings, 5 documents: an alignment object typed as a competency. Each
of these is a ceterms:Course whose ceterms:teaches values carry exactly
four properties — ceterms:framework, ceterms:frameworkName,
ceterms:targetNode, ceterms:targetNodeName — which is the canonical
ceterms:CredentialAlignmentObject shape, and nothing else. Each is typed
ceasn:Competency. CTDL declares all four properties for
ceterms:CredentialAlignmentObject and ceterms:FinancialAlignmentObject
only, and ceasn:Competency has no rdfs:subClassOf path to either, so all
four fall outside their declared domain on every such node — four findings per
node, 34 nodes. ceterms:teaches ranges on both ceasn:Competency and
ceterms:CredentialAlignmentObject, so the reference itself is fine; only the
@type is wrong. Changing it to ceterms:CredentialAlignmentObject clears all
136. Verified by reading the nodes in all five cached documents.
22 findings, 22 documents: a blank node that is not there. Each is a
ceterms:TransferValueProfile document containing one nested
ceterms:Course node whose ceterms:ownedBy names a blank node identifier
that the document never defines. Verified mechanically against the bytes of
all 22: each document defines exactly one blank node and references two, and
the undefined one is always the ownedBy target. Blank node identifiers are
scoped to the document they appear in, so this cannot be settled by supplying
anything — there is no document anywhere that _:96953f6f-… could be found
in — which is why --resolve changed nothing here. Four of the identifiers
recur across different documents, which points at a publishing pipeline
emitting a stable internal id for an organization whose node never makes it
into the output.
1 finding, 1 document: an outcome-data property on the profile that holds
the data. A ceterms:LearningOpportunityProfile carries a nested
ceterms:AggregateDataProfile whose qdata:relevantDataSet points at a data
set. qdata:relevantDataSet declares 61 classes in its domain — credentials,
organizations, learning opportunities, jobs, occupations — and
ceterms:AggregateDataProfile is not among them, so the property sits one
level deeper than either published encoding allows. This is the one finding
worth arguing about, because the property's own rdfs:comment reads "Data Set
on which earnings or employment data is based", and the only class in CTDL
that holds earnings and employment data is the one its domain omits. It is
published as an ERROR anyway, for a measured reason: the QData encoding at
credreg.net/qdata/schema/encoding/json, which is where this term lives,
declares the identical 61-class domain and also omits
ceterms:AggregateDataProfile. Two published sources agree with each other
and the document differs from both, which is the line this project draws
between an ERROR and a disposition. The nearest class that is in the domain
is the LearningOpportunityProfile that owns the profile. Readers should know
the base rate: qdata:relevantDataSet appears twice in all 2,859 documents
fetched for this run, and both uses are on ceterms:AggregateDataProfile.
A check on the snapshot itself. Because two of the three shapes turn on
what the schema declares, the vendored snapshot was re-fetched from
credreg.net during verification and is byte-identical to the copy this
tool ships (SHA-256 a2dd28cb…, the hash already recorded in
vendor/SOURCES.md). None of these findings is an artefact of a stale
snapshot.
155 references remain UNVERIFIABLE, 4.7% of the 3,326 the tool started with. They are not a backlog of documents nobody fetched: exactly one is a Registry resource that could have been supplied, and it is the neighbour that answered HTTP 404. The rest are outside the one hop this survey defined.
| What the unsettled reference points at | Findings |
|---|---|
| Registry /resources/ URI whose document was not supplied | 1 |
| credreg.net term | 48 |
| other host | 106 |
The 48 credreg.net references are CTDL vocabulary terms, which are concept identifiers rather than documents to fetch. The 106 on other hosts are alignment targets on publishers' own websites, which this survey does not follow and does not record by name. So the honest ceiling on this method is not 95% — it is that one referenced Registry document out of 1,660 was beyond reach, and everything else left unsettled was never a Registry document in the first place.
The 1,200 documents come from 169 distinct publishers. Concentration is real but not extreme: the most frequent accounts for 213 of 1200 (18%), and the five most frequent for 492 of 1200 (41%).
The ERRORs are more concentrated than the corpus. All 159 come from three publishers: one contributed the 5 courses with 136 findings, one the 22 transfer value profiles with 22, and one the single outcome-data finding. Both of the larger groups look like one pipeline emitting the same shape repeatedly rather than 27 separate mistakes, which is the useful thing to notice: at this scale a structural defect is usually a property of a publishing tool, not of a document.
The 2026-08-15 run drew 120 documents with a different seed against a corpus of a different size, so this is a comparison of two samples, not a time series. It is comparable in the way that matters — the earlier cache was re-validated by this same tool version — but a difference between the columns is at least as likely to be sampling as change in the Registry.
| Measure | 120 documents (2026-08-15) | 1,200 documents (2026-08-21) |
|---|---|---|
| Documents with no findings, alone | 3 of 120 (2%) | 94 of 1200 (8%) |
Documents with at least one ERROR, with --resolve |
1 of 120 (1%) | 28 of 1200 (2%) |
Documents carrying CONCEPT_RANGE_CONFLICT |
36 of 120 (30%) | 282 of 1200 (24%) |
Documents carrying CTID_NOT_UUIDV4 |
6 of 120 (5%) | 42 of 1200 (4%) |
| Share of findings that were UNVERIFIABLE, alone | 86% | 85% |
UNVERIFIABLE findings settled by --resolve |
294 of 299 (98%) | 3171 of 3326 (95%) |
The two most load-bearing numbers barely moved. The UNVERIFIABLE share before
resolution is 86% and 85% — this really is what a Registry document looks like
to a validator that will not guess — and the settle rate is 98% and 95%. The
clean-document share rose from 2% to 8%, which is almost entirely the
CredentialOrganization share of each draw rather than any change in quality.
None: there were no exclusions. All 1,200 drawn pages produced a document row.
- Two false-positive classes are gone from the tool. A range of
rdfs:Resourceno longer rejects everything, and versioning a resource with another of its own class is a disposition rather than a failure. Between them they accounted for 108 of the 267 ERRORs this run first raised, against 11 published documents that had done nothing wrong. This is the second consecutive survey whose main result was a bug in the validator, which is an argument for running the tool against real corpora before trusting it, not an argument that the corpus is clean. Conflict 5 in the README is now handled rather than deliberately unhandled, on the grounds set out above, and with the single-publisher base rate stated rather than buried. - The harness stopped publishing a number it could not defend. The request
counter lost an interrupted run's tally and reported 1,770 for work costing
at least 2,861. It is now banked after every request, the neighbour tallies
are counted off the cache rather than off a per-run counter, and a
referenced document that was neither fetched nor recorded as failed is
counted as
unresolvedinstead of disappearing. --resolveearned its ADR. It settled 95% of the unknowns on a 1,200-document corpus and introduced no new failure, which is exactly the additive property ADR 0004 claimed and could not previously demonstrate at scale.- Nothing is filed with anyone. No issue, pull request or report has been opened against any publisher, against Credential Engine, or against the Registry on the strength of this run. The two schema disagreements it turned up are described here and encoded as dispositions; that is the whole of the action taken.
uv run python tools/registry_survey.py --sample 1200 --seed 20260821 \
--resolve-cap 100000 --cache .registry-cache-2026-08-21 \
--out docs/findings/2026-08-21-registry-survey-at-scale.json
The same seed draws the same page numbers against the same X-Total; the
corpus moves, so a fresh draw against today's X-Total is a different sample
and is meant to be. Add --from-dir to re-derive this evidence file from the
cache, byte for byte, with no network: the draw, the robots outcome, the
request count and every fetch failure are read back from
<cache>/provenance.json. The cache is gitignored; the evidence file is not.
That claim was measured rather than asserted. Running both ways against this
cache produced 2,422,812 bytes and 2,422,811 bytes differing on exactly one
line — "from_cache": false against "from_cache": true, the field whose
job is to record which way the file was made. With that one field normalised,
both files hash to SHA-256
82656f17fee334a099b602d99f5a973bd056d3e18f6967620c7e8bc238233d0e. Every
other byte, including all 1,200 document records, is identical.