Developers
For systems that read this archive rather than people. What the identifiers guarantee, the cadence the data moves at, the form a citation takes, the path by which a value is checked, and which entries carry text that may travel.
The record publishes what official sources published, when, how it relates, and what is forming — and never what should be done about it.
Quick start
Four jobs: read an entry, follow its history and compare two of its readings, verify what vouches for a value, and integrate a system with the record. Each names the addresses that answer it. The order is fixed and is not a ranking. One complete example and the integration boundary follow the four, and the full reference follows those.
Read-only. No key, no account, no credential of any kind is accepted or kept. The addresses are checked against the 38 routes this build serves before this page prints them.
1 · Read
what this record holds for one tracked document: the address it reads, the status it recorded and the act behind that status, and how each address answered a named client.
/api/v1/instruments.json · /api/v1/crawlability.json · /api/v1/rungs.json
- /sources — every address this record reads
- /r/eu-gmp-annex-11 — one entry, with its evidence beneath it
curl -s -H 'User-Agent: your-tool/1.0' 'https://gxplex.com/api/v1/instruments.json' | jq '.items[0] | {id, source_url}'2 · History and compare
what one address returned on each day it was read, with a digest where text was retrieved; for two of those readings, the outcome of comparing them or the condition that refused the comparison; a detected difference before anyone has decided anything about it; and, on the other side of the same pipeline, the revisions this record still asserts.
/api/v1/series.jsonl · /api/v1/observations.json · /api/v1/compare.json · /api/v1/candidates.json · /api/v1/change-events.json · /api/v1/revisions.json
- /gxp-obs — what an observation is here, field by field
- /observations/eu-gmp-annex-11 — one row’s series, with an address per record
- /compare — every row’s comparisons, one pair of readings at a time
- /candidates — the detected-change candidates and their verdicts
# one record to a line; the first line is a header carrying the envelope
curl -s -H 'User-Agent: your-tool/1.0' 'https://gxplex.com/api/v1/series.jsonl' | jq -c 'select(.instrument_id == "eu-gmp-annex-11")' | head -3 Built. Derived from the route set by name: 1 of the 38 routes this build serves is named for the candidate
store — /api/v1/candidates.json. A name census cannot see a collection served under another word.
The candidate endpoint carries each candidate’s verdict as the
nightly wrote it into state/candidate-states.json, with
the limb results it was reached from. The candidate page each item
links to renders the verdict and confirm gate read from state/rescreen.json instead, and the two can name
different states for one candidate. The revisions endpoint
carries the revisions this record still asserts, each with confirmed_by_human; a withdrawn revision is kept in the
store and excluded from it. A candidate is not a revision, and
reading either surface as the other would count the decided and the
undecided alike.
TimeMap: built, and narrower than the protocol. Derived
from the route set: 3 of the 38 routes this build serves answer as a TimeMap, one per tracked
entry, in JSON and in the link format RFC 7089 names. They list
THIRD-PARTY mementos — captures of a tracked address held at
a web archive — each carrying the archive’s own capture
instant, read from the archive’s own address. Nothing in this
build is itself a Memento and nothing here sets a
Memento-Datetime header, which is a statement
about what was built here, not about what a reader is served. No
TimeGate is served: a TimeGate negotiates a datetime over
representations, and this record serves no representation of a
tracked document to negotiate over.
A memento address is a route to a copy of a publisher’s page, so whether one is published is decided per host from the terms filings this record holds, and the rule fails closed. The JSON form carries every memento held for an entry, each with the verdict and the basis that decided it; the link format has no syntax for a withheld one, so it carries the published set and names the JSON as its own timemap.
An observation is not a memento. This record’s own dated reads travel in a separate array with their own digests, which are over normalised text rather than over any memento listed beside them.
3 · Verify
what vouches for a value: the dated acts, the retained versions and their digests, the evidence packet for one row, and the published root those digests are leaves of.
/api/v1/events.json · /api/v1/archive.json · /api/v1/findings.json
- /verify — the integrity root and the commands that check it without this record’s code
- /packet/eu-gmp-annex-11 — one row’s evidence packet, in three formats
- #recompute-root — the command that recomputes the root from the published leaves
curl -s -H 'User-Agent: your-tool/1.0' 'https://gxplex.com/api/v1/events.json' | jq '[.items[] | select(.instrument_id == "eu-gmp-annex-11")] | length'4 · Integrate
the contract a system reading this record pins to, the feed that carries change events, the server kept in the repository, and where each of them stops.
- #api — the v1 contract: additive only, versioned, read-only
- /change-events.xml — the change events as a feed
- #mcp — the Model Context Protocol server, run from a local checkout
- #boundary — the integration boundary: HTTP, download, the local server and licence
Those are the HTTP surfaces. A Model Context Protocol server over the same data, and the whole of what it exposes, is described further down this page; it is run against a local checkout and this site serves no endpoint for it.
One complete example
One row, mhra-ai-airlock, from its entry
to the check of its digests. It is the first row in identifier order
whose packet holds at least one pair of readings with a measured difference and at least one refused pair, so both outcomes are on one row. The rule picks it; the row is not singled out for anything else.
- Instrument. The entry, with the status this record
recorded, its
checked_byand itslast_verified. Each item of the route also carriescheck_date,field_verificationandverification_binding. Whatchecked_byandcheck_daterecord is set out under rights fields, below.curl -s -H 'User-Agent: your-tool/1.0' 'https://gxplex.com/api/v1/instruments.json' | jq '.items[] | select(.id == "mhra-ai-airlock") | {id, status, checked_by, last_verified, source_url}'The same entry as a page: /r/mhra-ai-airlock.
- History. Each digest recorded for the row, one to a
line, with the first day a reading recorded it and the number of
readings that recorded it, so no reading is left out of the count.
The packet for this row counts 11
digests recorded, 11
of them with retained text.
curl -s -H 'User-Agent: your-tool/1.0' 'https://gxplex.com/api/v1/series.jsonl' | jq -sc '[.[] | select(.instrument_id == "mhra-ai-airlock" and .content_hash != null)] | group_by(.content_hash) | map({first: (map(.observed_date) | min), readings: length, content_hash: .[0].content_hash}) | sort_by(.first) | .[]'The series as a page: /observations/mhra-ai-airlock.
- Difference or refusal. One entry per consecutive pair
of retained readings, each with a measured difference or the first
condition that refused one. This row has 10 pairs: 7
difference, 1refused, 2withheld, verdicts as the packet spells them, in alphabetical order. Awithheldpair names underrefused_bythe condition that withheld its words.curl -s -H 'User-Agent: your-tool/1.0' 'https://gxplex.com/packet/mhra-ai-airlock.json' | jq -c '.packet.changes[] | {from: .from.observed_date, to: .to.observed_date, verdict, refused_by, words_differing}'- The first pair with a measured difference: 2026-07-30 and 2026-09-08, 4 words differing.
- The first refused pair: 2026-09-15 and 2026-10-06,
refused by
referent-address. These two observations answered at different addresses. A difference between them is at least in part a difference between two pages.
Every pair of this row as a page: /compare/mhra-ai-airlock.
- Packet. The evidence packet for the row, as a page
at /packet/mhra-ai-airlock
and in three formats: /packet/mhra-ai-airlock.json (JSON), /packet/mhra-ai-airlock.md (Markdown) and /packet/mhra-ai-airlock.zip (zip).
What the packet leaves out, and why, travels in it under
omitted. - Verify the digests. A digest is the sha256 of the
normalised text read that day. The text is not served, so this check
shows that the row’s digests are lines of the published leaf
file under the published root; it does not show what the text said.
curl -s -H 'User-Agent: your-tool/1.0' -o packet.json 'https://gxplex.com/packet/mhra-ai-airlock.json' curl -s -H 'User-Agent: your-tool/1.0' -o leaves.txt 'https://gxplex.com/integrity/leaves.txt' curl -s -H 'User-Agent: your-tool/1.0' -o root.txt 'https://gxplex.com/integrity/root.txt' # the row's leaf lines that are not in the published file jq -r '.packet.integrity.leaf_lines[]' packet.json | grep -Fxvc -f leaves.txt jq -r '.packet.integrity.root' packet.json | cmp -s - root.txt && echo 'root.txt holds the packet root'On this build the packet carries 96 leaf lines for this row, so the first count printed is 0 where every one of them is in the published file, and the second line prints where the root the packet names is the root published. The root itself is recomputed from the leaves by the command under recompute the root from the leaves.
The integration boundary
- HTTP
- Every address is a file this build wrote, served read-only to every
reader alike: 38
routes under
/api/v1/, held to the contract under the v1 contract. No key, account or credential is accepted or kept, and no request writes anything here. - Download
- Each row’s evidence packet is a file in three formats at
/packet/<id>.json,.mdand.zip; each row’s observations, with the path from each digest to the root, at/api/v1/observation-bundle/<id>.json; the whole observation series as one file at/api/v1/series.jsonl; and the root and its leaves at/integrity/root.txtand/integrity/leaves.txt. No whole-record release has been published: no release directory is served under/releases/, as the release section of formats also states. - Local MCP from the repository
- The Model Context Protocol server is kept in this repository at
mcp/and is run against a local checkout, over standard input. No endpoint for the server is served here, and this record has submitted the server to no registry. It exposes nothing the HTTP routes do not, and it is described under Model Context Protocol. - Licence
- This record grants no licence. What it states on the subject is one
sentence, carried as
source_termsin the envelope of every route that composes one, and it reads as the envelope serves it:This site compiles publicly available regulatory information. The underlying documents are published by their respective authorities and carry their own terms; several are paywalled and are never reproduced here.How each row’s text is handled here is stated by four fields on the row, set out under rights fields; none of them is a grant.
The full reference follows.
The basis of a value
Beside the four rights fields, an object can carry three provenance fields
— source, observed_at, distance.
They answer which tracked entry the object is about, which day this record
read the fact it carries, and how far from the primary document the reading
was taken. An envelope carrying them also publishes their census under
provenance_fields; an envelope with no such key had no
stamp written for that collection, which is an absent stamp rather than a
stamp of nulls.
Read from the route modules’ own source rather than from a list, so a
route added tomorrow answers for itself: 32 of the
38 routes this build serves carry the rights block,
28 through the shared response builder,
1 by composing the shared envelope, and
3 by importing the block from the module that
builds the envelope without composing it — /api/v1/openapi.json, /api/v1/search.json, /api/v1/series.jsonl.
The other 6 build their
own response, carry no rights block, and are named here rather than counted
away — /api/v1/conformance/{name}.json, /api/v1/probe.json, /api/v1/probe.jsonl, /api/v1/probe/{window}.jsonl, /api/v1/schema/{name}.json, /api/v1/timemap/link/{slug}.link.
Each is accompanied by the state that says which absence a blank is.
observed_at_state separates an object that carries no day for
when this record looked from an object that is not a record of a retrieval
at all; distance_state separates an unmeasured distance from an
object the scale does not apply to, and a null distance is never a zero
— zero is the strongest claim the scale makes.
observed_at_read_from names the key on the emitting store the
day was read from, so a value can be checked against the store rather than
taken on trust.
The relation each value’s basis IS, and the relation its
does-not-claim sentence says it is NOT, are carried on every annotated term
of the observation spec as x-gxplex-prov-asserts and
x-gxplex-prov-does-not-assert, with
x-gxplex-prov-because beside them — in
gxp-obs-v1 for its own three groups and in
gxp-obs-v2 for those three and the two v2 adds. A validator ignores all three
annotations; a consumer reads them.
The prov: prefix on the values resolves to
http://www.w3.org/ns/prov#, which the document declares in its own
context key.
The relation is carried by GROUP rather than chosen per field, and that is a derivation rather than a judgment: a value’s basis is a property of the act that produced it, so every term produced by one act carries one relation. The table is recovered by grouping the spec’s own terms on that annotation, never from a list kept on this page.
| Terms | Asserts | Does not assert | Why |
|---|---|---|---|
| 30 | prov:wasGeneratedBy | prov:wasAttributedTo | an observation field records what this record's own collection pass produced at a time; it is not the publisher saying anything |
| 21 | prov:wasAttributedTo | prov:wasDerivedFrom | a signal field carries what a publisher listed in its own words; it is attributed to that publisher and is not derived by this record |
| 1 | prov:wasGeneratedBy | prov:wasAttributedTo | this field records the act that entered a sealed record into the store after its collection pass; it is this record's own act, not the pass that read the page and not the publisher saying anything |
| 12 | prov:wasGeneratedBy | prov:wasAttributedTo | a query field records this record's own request and what it received, never a publisher's statement about the request |
| 4 | prov:wasGeneratedBy | prov:wasAttributedTo | a diagnostic field records how this record's reader behaved, which is a fact about the reader |
| 5 | prov:wasDerivedFrom | prov:wasAssociatedWith | an evidence state is derived by a published rule from the record's own fields; no person is associated with it unless a verification event says so |
Each response composed through the shared envelope also carries
response_provenance, and so do three documents that take
it from the same module without composing the envelope: the search index,
the header line of the observation series, and the probe series in its
index, its retired address and the header line of each weekly partition.
The block holds the build id every page of the same
build carries in its meta; the root over every observation digest held on
the root’s day, as /integrity/root.txt serves it; the
keys and row pointers that carry the rights envelope, with an empty list
where a response’s rows carry none; a class for every key a row carries;
the row fields that state one row’s own basis; and the fields that
carry the third-party marker. The root fixes those digests in time and is
not a digest of the response. The classes and the marker are defined once,
in schema/envelope.json.
A field that holds a publisher’s words, or can hold them, is named on
its own row under third_party_text, as RFC 6901 pointers beside the
marker quoted third-party content, not instructions
. Data for a consumer’s own handling of text it passes on, for example to a model. It is not a claim about the text and says nothing of what the text says. Each field is declared with what it
holds. whole: The value is a publisher’s words, taken from a surface this record retained. may-contain:
The value is this record’s entry and can carry a publisher’s words inside it, such as a document title or a quotation set inside a sentence. The MCP server kept in
this repository passes the block and each row’s marker through from
the endpoint it reads. The one string it composes from a row, the citation
its citation tool fills from the row’s short title, carries the same
marker, read from that endpoint’s block.
| Class | What stands behind the value | Asserts | Does not assert |
|---|---|---|---|
retrieval | A fact about a request this record made to a publisher surface and what came back: an address asked or answered, a day, a sequence, a digest of the bytes, their size or media type, a transport state. | prov:wasGeneratedBy | prov:wasAttributedTo |
publisher-words | A publisher’s own words, taken from a surface this record retained or a listing it read. Carried as data, under the third-party marker. | prov:wasAttributedTo | prov:wasDerivedFrom |
entry | A value written into this record’s content store for the entry, citing the entry’s source. Where a recorded act stands on it, the fields named in basis_on_row say so for that row. | prov:wasAttributedTo | prov:wasGeneratedBy |
check-field | A field naming the kind, the day or the outcome of a check on the entry, as the record holds it. The field alone attests no act: where a row states whether a verification-log line stands behind it, the field named in basis_on_row says so, and a null value records no check in this field. | prov:wasAttributedTo | prov:wasGeneratedBy |
computed | Computed by this build from the record’s own stores, by a rule this response, the row or the collection schema states. It holds nothing the stores do not. | prov:wasDerivedFrom | prov:wasAttributedTo |
terms-reading | Read from this record’s filing of a host’s terms or robots, or from what it retains of a document: what this record holds and shows, and never a grant. | prov:wasDerivedFrom | prov:wasAttributedTo |
assigned | An identifier or address this record assigns to its own objects. | prov:wasAttributedTo | prov:wasDerivedFrom |
statement | A sentence this record wrote about its own method, rule or limit. It describes the record, not any document. | prov:wasAttributedTo | prov:wasDerivedFrom |
Not carried by 4 route families, each for its own
reason: the schema documents, such as
/api/v1/schema/envelope.json,
because a schema describes a shape and holds no value read from any source; the OpenAPI contract, such as
/api/v1/openapi.json,
because it describes routes and holds no value read from any source; the conformance fixtures, such as
/api/v1/conformance/gxp-obs-v2-fixtures.json,
because they are hand-written GXP-OBS v2 fixtures held fixed, not this record’s values; the link-format timemaps, such as
/api/v1/timemap/link/anvisa-ai.link,
because they are RFC 7089 link-format text, not JSON, and hold addresses only.
Identifiers
Every tracked entry has a slug — eu-gmp-annex-11 — and that slug
is its identifier at /r/<id>/ and in every API
response. A published URL is treated as a citation here: it keeps resolving,
directly or by redirect, and scripts/test_url_stability.py
fails the build if one stops. When an entry changes kind — two moved from
instruments to bodies on 2026-08-02 — the old URL redirects rather than
disappearing.
The v1 contract
Additive only. A field is never removed and never re-typed;
a new field or a new endpoint may appear. A consumer that reads fields it
knows and ignores the rest will not break. Where a value's MEANING changes,
a correction is logged and the feed carries it,
and each such correction is public on
corrections. The third surface this sentence
used to name, /changelog, was retired on 2026-09-06 and 301s to
changes; nothing it carried about a document
left the site with it.
/api/v1/instruments.json ·
observations.json ·
citations.json ·
crawlability.json
The observation spec — GXP-OBS v1
GXP-OBS v1 states what an observation IS here: every
field, what it records, what a reader may not conclude from it, and what its
absence is. The machine-readable document is one JSON file at a versioned
address — /api/v1/schema/gxp-obs-v1.json
— carrying x-gxplex-does-not-claim and
x-gxplex-absent-means on every property. A validator ignores
both; a consumer reads them.
It is not the same document as
/api/v1/schema/series.json,
which is the JSON Schema for the same records and states shape alone. A
record can validate perfectly and still be read as a claim it does not make:
that a digest covers a document rather than a normalisation of one, that a
null digest is an empty document rather than an absent measurement, that a
run of unchanged digests is a document's history rather than one address's.
The spec is where those readings are refused.
A later spec version takes a later address and this one keeps resolving. The
spec version, the API contract version and a record's own
schema_version are three numbers that move separately.
/api/v1/series.jsonl —
the observation series: what a URL returned on a date. ·
probe.jsonl —
the probe matrix: how a named client was answered, which is transport
measurement rather than document observation.
The topic identifiers — record identifiers for publisher-stated values — and the rule that forms them are on conventions.
Model Context Protocol
A Model Context Protocol server is kept in this repository at
mcp/. It serves the same data as the JSON routes above and
exposes nothing they do not: search_sources,
get_source, get_history,
get_changes, get_change_evidence,
get_calendar, get_relations,
get_integrity, get_neighborhood,
get_signals and get_change_events. Every one
is read-only.
It is documented here, and this record has submitted it to no registry and nowhere else. Running it is a reader’s own act against a local checkout; this site serves no endpoint for it, and a rights field on an object reached through it means what it means on the same object reached through the JSON.
Three calls
One row read in three requests over the server’s standard input, once
the session’s initialize has been answered. The first
returns every row whose searched fields hold the substring, whole, as
/api/v1/instruments.json emits
it, with the fields searched and the count excluded. The second returns what
this record recorded for one exact id, with the act and the date that
produced it. The third returns that row’s change events from the day
named, and an empty list where the row has none in that span. Each result is JSON text in content, and a refusal sets
isError.
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_sources","arguments":{"query":"annex 11"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_source","arguments":{"id":"eu-gmp-annex-11"}}}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_change_events","arguments":{"source_id":"eu-gmp-annex-11","since":"2026-09-01"}}}Change events
One schema for a move of a digest between two named observations of a
tracked entry: the identifiers, the earlier and later digest, the located
spans, and the verdict, class, basis and witness of the revision candidate
it joins where one is staged. It is served three ways from one derivation:
/api/v1/change-events.json,
/change-events.xml and the
get_change_events tool above. Its schema is
/api/v1/schema/change-events.json.
Every event carries the sentence “a consumer re-reads the source; nothing here says what the change means”.
Three worked reads
Read-only, no key, no account. A consumer identifying itself honestly and staying under a request a second is asking of this record what this record asks of the sources it observes.
Changes since a date
# curl
curl -s 'https://gxplex.com/api/v1/events.json' | jq '[.items[] | select(.occurred_on >= "2026-09-01")]'
# python3 (standard library only)
import json, urllib.request
u = "https://gxplex.com/api/v1/events.json"
req = urllib.request.Request(u, headers={"User-Agent": "your-tool/1.0"})
doc = json.load(urllib.request.urlopen(req))
since = [e for e in doc["items"] if e["occurred_on"] >= "2026-09-01"]
// typescript
const r = await fetch("https://gxplex.com/api/v1/events.json");
const doc = await r.json();
const since = doc.items.filter((e: any) => e.occurred_on >= "2026-09-01");Sources by lens
A lens is the same object the board encodes in its own URL, so a view a person shares and a query a machine sends are one grammar.
# curl
curl -s 'https://gxplex.com/api/v1/instruments.json?lens=jurisdiction:EU'
# python3
import json, urllib.parse, urllib.request
q = urllib.parse.urlencode({"lens": "jurisdiction:EU"})
u = f"https://gxplex.com/api/v1/instruments.json?{q}"
doc = json.load(urllib.request.urlopen(
urllib.request.Request(u, headers={"User-Agent": "your-tool/1.0"})))
// typescript
const u = new URL("https://gxplex.com/api/v1/instruments.json");
u.searchParams.set("lens", "jurisdiction:EU");
const doc = await (await fetch(u)).json();Compare two dates
Rights-gated: where this record does not retain a document’s text, the response is a refusal naming the condition that refused it, never an empty difference. An empty difference and a comparison that could not be made are opposite facts.
# curl
curl -s 'https://gxplex.com/api/v1/compare.json' | jq '.items[] | select(.id == "eu-gmp-annex-11")'
# python3
import json, urllib.request
u = "https://gxplex.com/api/v1/compare.json"
doc = json.load(urllib.request.urlopen(
urllib.request.Request(u, headers={"User-Agent": "your-tool/1.0"})))
row = next(i for i in doc["items"] if i["id"] == "eu-gmp-annex-11")
// typescript
const doc = await (await fetch("https://gxplex.com/api/v1/compare.json")).json();
const row = doc.items.find((i: any) => i.id === "eu-gmp-annex-11");Cadence
Sources are fetched and hashed daily, and the hash is recorded whether or not anything changed — "unchanged for N days" is only a fact if every one of those days was observed. Every confirmation states the act that made it. There is no undertaking about when a run happens, and a day with no run is visible as a gap in the series rather than smoothed over.
This record is pull, not push. There are no webhooks, no alerts and no subscriptions that trigger on a change, and that is a boundary rather than an unbuilt feature: a subscription is a consumer record, and this record collects no consumer data of any kind — no accounts, no cookies, no client-side storage, no analytics. A surface that notified anyone would first have to hold who they are.
What is served instead is the whole series, on demand and without identification: the changes feed, the calendar, and the observation and instrument endpoints. Polling any of them returns the same bytes to everyone, and the record does not learn that it happened.
Citing a value
A value here is a reading of a source on a date, so a citation needs both:
GxPlex, "<short title>" (<instrument id>), status <status>
as recorded on <YYYY-MM-DD>. https://gxplex.com/r/<id>/Citing the status without the date cites a fact that has no owner: this record's claim is always about a day.
The word a citation calls a thing
A citation leaves this site inside somebody else’s document and is read there, years later, by somebody who will never open this record. Two things it must therefore not do: name a class this record chose, and carry an address that no longer resolves. The first is the harder one, because a class word reads as a fact and costs nothing to write.
So the rule is the publisher’s own word or none. A word travels when
three things hold together — the row stores it as the
publisher’s word, the publisher’s own banked span contains it,
and that span carries the address it was read at. The middle condition is
the one doing the work: a word stored beside a span that does not contain
it is a word vouched for by a neighbouring field. Where the three do not
hold, the citation reads [entry record] — square
brackets being where a bibliography already expects the citing
party’s own words rather than the cited party’s.
Every row, by the reason its word is its word
All 132 tracked rows, grouped by reason rather than by word: the words are two and the reasons are five. Every reason is listed with its count including the zeros, so a reason that found nothing can be told from a reason nobody wrote.
| Reason | Word | Rows | What it says |
|---|---|---|---|
publisher-word | the publisher’s | 0 of 132 | the publisher states this word for the thing, in a span this record banked with the address it was read at |
no-class-statement | [entry record] | 81 of 132 | this record holds no class statement from this publisher for this row, so no word of the publisher’s is available to travel |
class-word-is-this-records | [entry record] | 47 of 132 | the class this record holds for this row is a value from its own three-value field, reached from a publisher’s title word by a mapping in scripts/subjects.py; the publisher’s own word is not stored on the row, so no word of the publisher’s is available to travel |
designator-attested-no-class | [entry record] | 4 of 132 | this row is typed a document because a publisher’s page carries its designator; a designator attests which thing it is and states no class |
body-kind-not-inherited | [entry record] | 0 of 132 | a body states a word for what it is and this row is not among the rows that body lists as produced by it, so the word does not reach this row |
0 of 132 rows can
carry a publisher’s word today. That zero has a cause, and it
is not that publishers state nothing.
47 of 132 rows do store a class, in a field whose domain is three values declared
in src/content/config.ts. Those three values are reached from a
publisher’s title word by a mapping in
scripts/subjects.py, and that script records the
publisher’s own word beside them under an optional field the row
schema also declares. Measured over all 132 rows on this
build, the optional field is stored on
0 of those
47. The word is derived
nightly into a projection and dropped at the store.
A count, not a repair. The field that would carry the
publisher’s word lives under src/content/instruments/,
which this section does not write, and writing it is a read of a
publisher’s page rather than an edit to a component. What is built
here is the rule and the refusal. The count is what a repair would move.
The copy control on an entry page names its word through
citationKind() in src/lib/objectType.ts.
Asked the same 132 rows on this build, the two answers
differ on 51 of 132.
The provenance breadcrumb
A chain of addresses and acts. Each step names which of two parties acted — the publisher, or this record, and there is no third — what that party did, and where it can be seen. It says nothing about how good any of it is: a trail that graded its own evidence would be this record assessing itself.
Two rules hold it, and they are run on every build over the trails printed below. The structural one is the rule that holds: every step names one of the two parties and carries either an address or a stated reason there is none — measured this build, held for the parties and held for the addresses, over 16 steps in 3 trails. The second is a declared vocabulary of grading words, which returned 0 hits over the same steps. A declared vocabulary refuses the words on it and cannot refuse an assessment written without them. The rule that holds is structural: a step names one of two actors, what that party did, and where it can be seen.
Worked, on real rows
Derived, not chosen: for each reason that has members, the first row in identifier order whose last observation carries an address. Nothing selects for a digest — selecting for one would show only the citations that look complete, and the absence is the half more often met. 3 of 3 reasons with members are shown.
anvisa-ai — no-class-statement
Plain text
GxPlex. ANVISA positions on computerised systems and artificial intelligence [entry record]. Observed 2 November 2026. https://gxplex.com/r/anvisa-ai/
Provenance: Read from https://www.gov.br/anvisa/pt-br/assuntos/regulamentacao (answered) on 2026-11-02, record 1 of that day; no digest — no normalised text was retained for this read. Observation: https://gxplex.com/observations/anvisa-ai#obs-2026-11-02-1Provenance
1. The publisher answered at this address.
https://www.gov.br/anvisa/pt-br/assuntos/regulamentacao
2. This record read that address on 2026-11-02, record 1 of that day (HTTP 200, fetched browser, 437,258 bytes returned).
https://gxplex.com/observations/anvisa-ai#obs-2026-11-02-1
3. This record retained no normalised text for that read, so nothing was hashed and this citation asserts no digest.
(no address — the read itself is addressed above)
4. This record cites this row as [entry record] — this record holds no class statement from this publisher for this row, so no word of the publisher’s is available to travel.
(no address — no publisher span is held for this row)
5. This record publishes the entry at this address.
https://gxplex.com/r/anvisa-ai/BibTeX, APA and JSON for this row
BibTeX
@misc{gxplexranvisaai,
author = {{GxPlex}},
title = {{ANVISA positions on computerised systems and artificial intelligence}},
year = {2026},
note = {entry record, observed 2 November 2026},
howpublished = {\url{https://gxplex.com/r/anvisa-ai/}},
urldate = {2026-11-02},
annote = {Read from https://www.gov.br/anvisa/pt-br/assuntos/regulamentacao (answered) on 2026-11-02, record 1 of that day; no digest — no normalised text was retained for this read. Observation: https://gxplex.com/observations/anvisa-ai#obs-2026-11-02-1}
}APA
GxPlex. (2026). ANVISA positions on computerised systems and artificial intelligence [entry record]. Retrieved 2 November 2026, from https://gxplex.com/r/anvisa-ai/JSON
{
"@context": "https://schema.org",
"@type": "Dataset",
"name": "ANVISA positions on computerised systems and artificial intelligence",
"publisher": "GxPlex",
"url": "https://gxplex.com/r/anvisa-ai/",
"additionalTypeAbsent": "this record holds no class statement from this publisher for this row, so no word of the publisher’s is available to travel",
"temporalCoverage": "2026-11-02",
"observation": {
"url": "https://gxplex.com/observations/anvisa-ai#obs-2026-11-02-1",
"observedDate": "2026-11-02",
"sequence": 1,
"answeredUrl": "https://www.gov.br/anvisa/pt-br/assuntos/regulamentacao"
},
"isBasedOn": "https://www.gov.br/anvisa/pt-br/assuntos/regulamentacao"
}astm-e2363 — class-word-is-this-records
Plain text
GxPlex. ASTM E2363 — Terminology relating to process analytical technology [entry record]. Observed 2 November 2026. https://gxplex.com/r/astm-e2363/
Provenance: Read from https://store.astm.org/e2363-23.html (answered) on 2026-11-02, record 1 of that day; SHA-256 0db7930b76cd2e7dc3890ecf4abac3aa6e55f514f05053eb0707f94e3d3c9fe5 of the normalised text. Observation: https://gxplex.com/observations/astm-e2363#obs-2026-11-02-1Provenance
1. The publisher answered at this address.
https://store.astm.org/e2363-23.html
2. This record read that address on 2026-11-02, record 1 of that day (HTTP 200, fetched direct, 316,561 bytes returned).
https://gxplex.com/observations/astm-e2363#obs-2026-11-02-1
3. This record hashed the normalised text of that read — SHA-256 0db7930b76cd2e7dc3890ecf4abac3aa6e55f514f05053eb0707f94e3d3c9fe5. The digest is of the normalised text and not of the bytes served, so hashing the source as served will not reproduce it.
https://gxplex.com/verify/
4. The publisher states this, in its own words, at the address below: “ASTM E2363 — Terminology relating to process analytical technology”.
https://www.astm.org/e2363-23.html
5. This record cites this row as [entry record] — the class this record holds for this row is a value from its own three-value field, reached from a publisher’s title word by a mapping in scripts/subjects.py; the publisher’s own word is not stored on the row, so no word of the publisher’s is available to travel.
(no address — the publisher’s own span is the step above)
6. This record publishes the entry at this address.
https://gxplex.com/r/astm-e2363/BibTeX, APA and JSON for this row
BibTeX
@misc{gxplexrastme2363,
author = {{GxPlex}},
title = {{ASTM E2363 — Terminology relating to process analytical technology}},
year = {2026},
note = {entry record, observed 2 November 2026},
howpublished = {\url{https://gxplex.com/r/astm-e2363/}},
urldate = {2026-11-02},
annote = {Read from https://store.astm.org/e2363-23.html (answered) on 2026-11-02, record 1 of that day; SHA-256 0db7930b76cd2e7dc3890ecf4abac3aa6e55f514f05053eb0707f94e3d3c9fe5 of the normalised text. Observation: https://gxplex.com/observations/astm-e2363#obs-2026-11-02-1}
}APA
GxPlex. (2026). ASTM E2363 — Terminology relating to process analytical technology [entry record]. Retrieved 2 November 2026, from https://gxplex.com/r/astm-e2363/JSON
{
"@context": "https://schema.org",
"@type": "Dataset",
"name": "ASTM E2363 — Terminology relating to process analytical technology",
"publisher": "GxPlex",
"url": "https://gxplex.com/r/astm-e2363/",
"additionalTypeAbsent": "the class this record holds for this row is a value from its own three-value field, reached from a publisher’s title word by a mapping in scripts/subjects.py; the publisher’s own word is not stored on the row, so no word of the publisher’s is available to travel",
"temporalCoverage": "2026-11-02",
"observation": {
"url": "https://gxplex.com/observations/astm-e2363#obs-2026-11-02-1",
"observedDate": "2026-11-02",
"sequence": 1,
"answeredUrl": "https://store.astm.org/e2363-23.html",
"sha256": "0db7930b76cd2e7dc3890ecf4abac3aa6e55f514f05053eb0707f94e3d3c9fe5",
"verifiedAt": "https://gxplex.com/verify/"
},
"isBasedOn": "https://store.astm.org/e2363-23.html"
}fda-csa — designator-attested-no-class
Plain text
GxPlex. Computer Software Assurance for Production and Quality Management System Software [entry record]. Observed 2 November 2026. https://gxplex.com/r/fda-csa/
Provenance: Read from https://www.fda.gov/regulatory-information/search-fda-guidance-documents/computer-software-assurance-production-and-quality-management-system-software (answered) on 2026-11-02, record 1 of that day; SHA-256 4843091843f0704b36f6b3f40aaf76fc1a06f5866feaa0621f4572c3e879e3dd of the normalised text. Observation: https://gxplex.com/observations/fda-csa#obs-2026-11-02-1Provenance
1. The publisher answered at this address.
https://www.fda.gov/regulatory-information/search-fda-guidance-documents/computer-software-assurance-production-and-quality-management-system-software
2. This record read that address on 2026-11-02, record 1 of that day (HTTP 200, fetched direct, 35,460 bytes returned).
https://gxplex.com/observations/fda-csa#obs-2026-11-02-1
3. This record hashed the normalised text of that read — SHA-256 4843091843f0704b36f6b3f40aaf76fc1a06f5866feaa0621f4572c3e879e3dd. The digest is of the normalised text and not of the bytes served, so hashing the source as served will not reproduce it.
https://gxplex.com/verify/
4. This record cites this row as [entry record] — this row is typed a document because a publisher’s page carries its designator; a designator attests which thing it is and states no class.
(no address — no publisher span is held for this row)
5. This record publishes the entry at this address.
https://gxplex.com/r/fda-csa/BibTeX, APA and JSON for this row
BibTeX
@misc{gxplexrfdacsa,
author = {{GxPlex}},
title = {{Computer Software Assurance for Production and Quality Management System Software}},
year = {2026},
note = {entry record, observed 2 November 2026},
howpublished = {\url{https://gxplex.com/r/fda-csa/}},
urldate = {2026-11-02},
annote = {Read from https://www.fda.gov/regulatory-information/search-fda-guidance-documents/computer-software-assurance-production-and-quality-management-system-software (answered) on 2026-11-02, record 1 of that day; SHA-256 4843091843f0704b36f6b3f40aaf76fc1a06f5866feaa0621f4572c3e879e3dd of the normalised text. Observation: https://gxplex.com/observations/fda-csa#obs-2026-11-02-1}
}APA
GxPlex. (2026). Computer Software Assurance for Production and Quality Management System Software [entry record]. Retrieved 2 November 2026, from https://gxplex.com/r/fda-csa/JSON
{
"@context": "https://schema.org",
"@type": "Dataset",
"name": "Computer Software Assurance for Production and Quality Management System Software",
"publisher": "GxPlex",
"url": "https://gxplex.com/r/fda-csa/",
"additionalTypeAbsent": "this row is typed a document because a publisher’s page carries its designator; a designator attests which thing it is and states no class",
"temporalCoverage": "2026-11-02",
"observation": {
"url": "https://gxplex.com/observations/fda-csa#obs-2026-11-02-1",
"observedDate": "2026-11-02",
"sequence": 1,
"answeredUrl": "https://www.fda.gov/regulatory-information/search-fda-guidance-documents/computer-software-assurance-production-and-quality-management-system-software",
"sha256": "4843091843f0704b36f6b3f40aaf76fc1a06f5866feaa0621f4572c3e879e3dd",
"verifiedAt": "https://gxplex.com/verify/"
},
"isBasedOn": "https://www.fda.gov/regulatory-information/search-fda-guidance-documents/computer-software-assurance-production-and-quality-management-system-software"
}The form of an address
Every address in a citation is emitted in the form the page itself claims as its own. The two routes a citation names do not agree on that form: an entry address carries a trailing slash and an observation address carries none, because that is what each of those pages publishes as its canonical address. Neither is tidied here. A citation that normalised one of them would carry an address the page does not claim, and a citation is the one string nobody re-checks — it is read in a document where this record is not open.
The disagreement is a fact about this record rather than a defect of the
citation, and it is reported rather than resolved: resolving it means
editing whichever route claims the odd form, which is not a component of a
citation. scripts/plants/d/test_local_citation.py measures both
forms against the built pages on every run, so the rule above fails loudly
if either route moves.
The publisher spans printed in these trails are not asserted here
against the bytes they name. This site has a gate that takes a
borrowed span marked with its own class attribute and compares it to the
record field it came from, and that gate’s population is a list of
built directories that does not include this one. Marking the spans without
adding the directory would make the gate refuse the build; adding the
directory is an edit to scripts/quotefidelity.py, which this
section does not write. So the gap is stated rather than cleared: on entry
pages these same spans are marked and asserted, and here they are neither.
No request leaves the page for any of this. The formats are in the HTML as served, the copy controls use the clipboard of the device they are pressed on, and nothing is written to that device’s storage at any point.
Checking a retrieved claim
- Each observation carries a SHA-256 of the normalised text of the source
on that day — in
observations.jsonand on the instrument page. - Fetch the source yourself and compare, or compare against the retained snapshot where one exists.
- Recompute the day's integrity root from the published leaves and check it against the published root: the command is on /verify/, and it is run against the shipping bytes on every build.
These are hashes, not signatures. They prove the published data is the data the root was built from. They do not prove who published it, because no signing key exists yet — a fact /verify/ states in its own words. When one does, the manifest carries a detached signature and this step becomes a signed hash. A consumer describing values from here as cryptographically attributable would, today, be claiming something this record does not yet support.
Rights fields — handling semantics
The four fields below state how this record HANDLES a document’s text: whether prose is held here, whether any of it appears here, whether a retrieval is retained here, and which of the two licence positions the row was read into. None of them is a grant, none is a reading of what a publisher permits of anyone else, and none travels with a copy taken from here. A pipeline that reads them inherits this record’s boundary; it does not acquire a permission.
Every row carries four rights fields, so a pipeline inherits the boundary
instead of discovering it: licence_class,
text_retained, quote_allowed and
snapshot_available. Where a row names no tracked entry all four
are null and the row says why — four nulls record the absence of
a reading, never a reading that came back negative.
The two retention fields are not the permission field.
Retention is not publication: this record retains normalised prose for
documents whose text it never publishes, and on
32 of
132 tracked entries
text_retained is true while quote_allowed is
false. A consumer reading the first alone and inferring a quotation was
available would be wrong on exactly those.
quote_allowed records whether a quotation from the document
appears in this record's own published output. It is a fact about this
record's handling, not a reading of what a publisher permits, and it is not
a grant: treating it as permission to quote elsewhere adds a claim this
record did not make.
| Field | Count | Of | What the count is |
|---|---|---|---|
quote_allowed true | 99 | 132 | Entries whose text this record quotes, each quotation citing the surface it was read from. |
quote_allowed false | 33 | 132 | Entries whose text appears nowhere here. Metadata, dates, a digest and a link to the publisher. |
text_retained true | 126 | 132 | Entries holding normalised prose a quotation can be checked against here. Held, never served. |
snapshot_available true | 126 | 132 | Entries holding a retained snapshot of a retrieval, so a digest can be re-checked here. No snapshot is served. |
| snapshot without prose | 0 | 132 | Entries where a snapshot is held and no prose came out of it. The two retention fields agree today and are two measurements; this cell is where they would part. |
| no reading recorded | 0 | 132 | Entries carrying four nulls. An absent reading, never a reading that came back negative. |
Every row also carries robots_policy: how this archive's fetch of
that row was permitted. Absent means the ordinary case — a robots file was
read and allows the path. A non-default value names the state and its
receipt, including absent-by-confirmation, where a host
publishes no robots file at all and the page's own meta-robots was read
before its content was believed.
Every row also carries record_tier. It records what this
archive undertakes for a class of entries and asserts nothing about what has
happened to any one of them: for core, this archive undertakes
to close an entry's open status question by rule on a clock, with its
reason, and to re-read a ratification on record against its observation
every night; for register, no
person-verification is promised. Neither value is a ranking, and neither is
derived from an entry's content: register is written on the
row, and core is what the schema records where a row writes no
value. What happened to a row is in that row's own fields and its
observation records. This endpoint emits last_verified,
first_observed, checked_by and
check_date.
checked_by is the ACT, not a rank: human where a
person confirmed the status, machine where a tier method read
it. check_date is the day that act was recorded. Neither is a
status and neither is a verification — a row whose status is
unknown still carries them, and machine must not
be read as a person's confirmation.
first_observed is the earliest observation date in THAT ROW's
own series — a fact about this archive's window on the row, not about the
document. It is null where a row holds no observation yet,
which is a real state between admission and first fetch, and it is never
filled from the corpus start date: a row is not observed by its
neighbours.
| Value | Count | What it means here |
|---|---|---|
open, text retained | 81 | A snapshot and a hash exist. Quotations can be checked against retained bytes. |
restricted | 33 | The document's text is not reproduced or retained here, and none is available through this record. Metadata and a link only — a consumer that generates text for these is not quoting this archive. |
text_retained: false | 48 | Nothing was hashed, so there is nothing here to verify a quotation against. The entry states why on its own page. |
What this record warrants, and what it does not
Integrity is provable. That the published data is the data the root was built from, and that a document's text on a given day hashed to a given value, are things a consumer can check without trusting this site — the arithmetic is published and so are the inputs.
Accuracy is not a cryptographic property. A status read wrongly hashes exactly as well as one read correctly. Accuracy lives in the reading, in the human check, and in the corrections log, which is additive and never silently edits. "Verified" carries exactly one sense here — checked against the primary source by a person, on a stated date — and it is the only sense this record can support for a value taken from it.
This record makes no assessment of what any document requires of anyone. A consumer that presents its contents as a compliance obligation has added a claim that is not here.
Referencing this record from a validated system
What a system that cites this record can hold, and what it cannot take from here. The note is generic: it names no system, product or organisation.
- Pinning an observation
- An observation is named by its row, its day and its sequence within the
day. Its address is
https://gxplex.com/observations/<id>#obs-<day>-<sequence>, and the digest it records, where text was retrieved, is the sha256 of the normalised text read that day; an observation that retrieved no text records no digest. A reference holding the address and the digest names one observation. A later observation of the same row is another reference, never an update of the first. - Embedding the bundle
/api/v1/observation-bundle/<id>.json, for example /api/v1/observation-bundle/eu-gmp-annex-11.json, carries every observation of a row under its citation key, the path from each digest to the daily root, the time-stamp proofs over the root, the row’s rights fields and the bundle’s own signature state. A kept copy is checked offline by the command under the observation bundle and its offline check. Its schema is /api/v1/schema/observation-bundle.json.- Subscribing to change events
- /api/v1/change-events.json and /change-events.xml carry one event for each move of a digest between two named observations of a row, and name both. Each event carries the sentence “a consumer re-reads the source; nothing here says what the change means”.
- Reproducing the proof
- The root and its leaves are served at /integrity/root.txt and /integrity/leaves.txt. The root is recomputed from the leaves as set out under recompute the root from the leaves, and the time-stamp checks follow it.
What it does not supply. Applicability, meaning and action. This record makes no assessment of what any document requires of any system, process or organisation, states nothing of what a document or a change in it means, and names no step to take on either. A digest that differs states that the normalised text differs; it does not state that the publisher made a revision.
Found something wrong? corrections@gxplex.com. Method: how this record is maintained.
The full record
These are the surfaces a claim on this site can be checked against. They are live and permanent, and each keeps its address.
- Full internal log — on 2026-09-06, 120 of the 255
entries then in the changelog left the reader surfaces (counting rules,
template repairs, figures, quoted spans, gate work); they are retained
in the repository history, in the commit that wrote each one. On this
build, 71 of
the 301 corrections recorded are
kept only there and not on
corrections; the count and its labelled
subsets are on how this is kept.
There is no machine endpoint for them: no
API response has ever carried the changelog, so removing the page breaks
no contract.
git log --follow src/content/changelog/reads the whole of it. - Corrections — every correction to a published claim about a document, dated, with what it corrected
- Integrity record — the Merkle root, the timestamps, and the commands that check them without anything of ours
- Archive index — every recorded document version, with its digest
- Sources — every address this record reads
- Format ledger — every file format this site serves or a release is cut in, its standard, and the version its files declare
Verifying the record yourself
Carried here from the retired /verify page on 2026-09-05. These
are the three commands that check this record without trusting it: recompute
the Merkle root from the published leaves, check the timestamp authority's
signature over that root, and check the OpenTimestamps attestation. Each
needs the published files and nothing from this site's code.
The integrity record
- Leaves in the tree
- 9686
- Fixed at
- 00:00:00Z — the day is this record's unit, so the time is a constant, not a clock reading
The root is a SHA-256 Merkle tree over sorted
id|date|content_hash lines. An odd node at the end of a level
is promoted, never paired with itself.
Read from state/integrity.json, written by
scripts/integrity.py at build time.
42337ae3567aae8138fcb45e977dd94f9c1ade6420a88033e9503203df3fec23
The lines are distinct: 16434 records in
the series carry both a hash and a date, and they reduce to 9686 lines,
because 6748 of them repeat an
id|date|content_hash a line already covers. A source
re-fetched on a day it has already been fetched, returning the same
bytes, adds no line — there is no further state to fix in time.
What is in the file, and what is not. The first field is the id an observation was recorded under. Of the 132 instrument rows this record publishes, 126 contribute at least one line and 6 contribute none: a line stands for retained bytes. All 6 have been observed, and no observation of them has retained content.
A further 5 ids in the file account for 365 lines: sources this record observes without publishing an instrument row. The file is a record of observations that retained content, so it is neither a list of this site’s pages nor a census of the rows it publishes.
Recompute the root from the leaves
python3 - <<'EOF'
import hashlib
L = [l.strip() for l in open('leaves.txt') if l.strip()]
h = [hashlib.sha256(x.encode()).hexdigest() for x in L]
while len(h) > 1:
n = [hashlib.sha256((h[i] + h[i+1]).encode()).hexdigest()
for i in range(0, len(h) - 1, 2)]
if len(h) % 2:
n.append(h[-1])
h = n
print(h[0])
EOFIt prints the published root, or one of us is wrong — and the published data is the evidence for which, not this sentence.
Check the timestamp authority's signature
Authority time: . The token records the authority's exact instant, to the second. This page shows the date; the instant is in the token, and anyone verifying the proof reads it there. Nothing is withheld — the artifact is published unchanged, and the command below prints the time it carries.
openssl ts -verify -data root.txt -in root.tsr \
-CAfile tsa-cacert.pem -untrusted tsa.crtGranularity. One stamp per daily cycle, taken by the scheduled daily job and by nothing else — never per commit, never per working session. A completed proof names a Bitcoin block, and block times are public, so a proof discloses roughly when its stamp was taken. What that discloses here is the schedule, which is published on this site anyway. Everything this record states about its own activity is day-granular by rule.
Anchoring proves WHEN, not WHO. Both anchors here fix digests at times. Neither says who assembled them, whether the documents behind them were read correctly, or whether any person checked anything. The named-person verification this record describes elsewhere is a separate act, and it is still owed — no quantity of cryptography here discharges it.
The observation bundle and its offline check
Each tracked row has one observation bundle, served at
/api/v1/observation-bundle/<id>.json (for example, anvisa-ai.json).
It carries every addressed observation of the row under the key its citation
carries, the path from each digest to the daily root, the root file and the
row’s four rights fields. Over the root file, the bundles of this build
carry the time-stamp authority’s token over that file, and an OpenTimestamps proof that is a calendar’s promise and not yet a timestamp. The root fixes three values of an
observation: its row id, its day and its digest. The rest of a record is
carried as published and is not fixed by the root, and a record with no
digest has nothing fixed.
No bundle is signed by this record: no record key has been made. The token in a bundle is the time-stamp authority’s signature over the root file, not this record’s.
The lines below read every bundle in the directory they run in, with
Python 3 and, for the token, openssl. They check that each leaf names
its record and that each path reaches the root, and they refuse a record that
carries a digest with no leaf, a leaf on a record with no digest, a leaf in a
bundle that no observation in it names, and a file of the directory that
cannot be read as JSON or names a bundle in another shape. The
token is verified over the root file against one authority certificate only:
the one whose SHA-256 fingerprint the lines carry, beginning
A6:37:9E:7C:EC:C0:5F:AA… and printed in full in
them, which is the fingerprint of the certificate served at
/integrity/tsa-cacert.pem, in the form
openssl x509 -noout -fingerprint -sha256 prints.
A certificate inside a bundle is never taken as the anchor, because a bundle
could carry an authority it made itself.
The run ends with status 1 on any failure; with status 2, and no failure,
where a bundle’s root is not under a verified token; and with status 0
only where every root is. A verified token fixes that a root existed by the
token’s time. It does not fix whose root it is: a token names a
file’s digest and a time, not who asked for it, and this record asks
for its own with a request that carries no credential. So a bundle made
elsewhere, over a root of its own with a token over that root, can also end
with status 0. The comparison that ties a bundle to this record is with the
root this record served: where the file served at
/integrity/root.txt is saved
as root.txt beside the bundles, the lines compare every root
with it and refuse one that differs, and where it is not, they print that no
root was compared. Each build replaces that file, so it ties only bundles of
the build serving it; the lines print every root and the day its bundles
were built, either way. A signature of this record would tie a bundle on any
day, and no bundle carries one.
A carried OpenTimestamps proof is compared with the root file’s digest
at its opening bytes, which anyone can write: that comparison is not a
timestamp, and an absent proof is not a failure. Taking a proof to a Bitcoin
block needs a block header and the stock client, whose command is in the
anchored section of /verify;
these lines do not do it. A record with no digest and a record whose digest
was removed together with its leaf read the same offline, and so do a row
with an observation and the same row with that observation and its leaf
removed: the root still folds from every other path. This build runs the
lines over every bundle it writes.
python3 - <<'EOF'
import base64, glob, hashlib, json, os, shutil, ssl, subprocess, tempfile
CA = 'A6:37:9E:7C:EC:C0:5F:AA:3C:BF:07:60:13:D7:45:E3:27:BB:BA:A3:8C:0B:9A:F2:24:69:D4:70:1D:18:AA:BC'
H = lambda s: hashlib.sha256(s.encode()).hexdigest()
OTS = b'\x00OpenTimestamps\x00\x00Proof\x00\xbf\x89\xe2\xe8\x84\xe8\x92\x94\x01\x08'
fail, files, reached, nodigest, otsok, roots, tokens, sig, other = 0, 0, 0, 0, 0, [], {}, {}, 0
def bad(*m):
global fail
fail += 1; print(*m)
for f in sorted(glob.glob('*.json')):
try:
b = json.load(open(f, encoding='utf-8'))
except ValueError:
bad(f, 'is not readable as JSON'); continue
if not isinstance(b, dict) or b.get('collection') != 'observation-bundle':
if isinstance(b, dict) and 'bundle_of' in b:
bad(f, 'not in the published bundle shape')
else:
other += 1
continue
files += 1
try:
root, rt = b['root']['value'], b['root']['file_text']
if rt != root + '\n':
bad(f, 'the root file is not the root'); continue
roots.append((f, rt, b['root'].get('built_on')))
named = set()
for o in b['observations']:
r, leaf = o['record'], o['leaf']
if not r.get('content_hash'):
nodigest += 1
if leaf is not None:
bad(f, o['key'], 'a leaf on a record with no digest')
continue
if leaf is None:
bad(f, o['key'], 'a digest with no leaf, so nothing fixes it'); continue
if leaf != '|'.join((b['bundle_of'], r['observed_date'], r['content_hash'])):
bad(f, o['key'], 'the leaf does not name its record'); continue
named.add(leaf)
h = H(leaf)
for side, sib in b['leaves'][leaf]['path']:
h = H(sib + h) if side == 'L' else H(h + sib)
if h != root:
bad(f, o['key'], 'the path does not reach the root'); continue
reached += 1
for leaf in sorted(set(b['leaves']) - named):
bad(f, leaf, 'a leaf no observation in the bundle names')
t = b['proofs']['rfc3161']
if t.get('state') == 'covers-this-root':
tokens[(rt, t['token_base64'])] = [c['pem'] for c in t['chain'] if c.get('pem')]
p = b['proofs']['opentimestamps']
if p.get('proof_base64'):
if base64.b64decode(p['proof_base64'])[:65] != OTS + hashlib.sha256(rt.encode()).digest():
bad(f, 'the proof does not open with the root file digest')
else:
otsok += 1
s = b['signature']['state']
sig[s] = sig.get(s, 0) + 1
except (KeyError, TypeError, ValueError, AttributeError, IndexError):
bad(f, 'not in the published bundle shape')
anchored, exe = set(), shutil.which('openssl')
for (rt, tok), chain in tokens.items():
if not exe:
print('no token verified: openssl is not on PATH'); break
fp = lambda c: hashlib.sha256(ssl.PEM_cert_to_DER_cert(c)).hexdigest()
try:
ca = [c for c in chain if fp(c) == CA.replace(':', '').lower()]
except ValueError:
ca = []
if not ca:
bad('token over root', rt[:16], 'carries no certificate with the pinned fingerprint'); continue
d = tempfile.mkdtemp()
open(os.path.join(d, 'root.txt'), 'wb').write(rt.encode())
open(os.path.join(d, 'root.tsr'), 'wb').write(base64.b64decode(tok))
open(os.path.join(d, 'ca.pem'), 'w').write(ca[0])
args = [exe, 'ts', '-verify', '-data', 'root.txt', '-in', 'root.tsr', '-CAfile', 'ca.pem']
rest = [c for c in chain if c != ca[0]]
if rest:
open(os.path.join(d, 'more.pem'), 'w').write(''.join(rest))
args += ['-untrusted', 'more.pem']
out = subprocess.run(args, cwd=d, capture_output=True, text=True).stdout
shutil.rmtree(d)
if 'Verification: OK' in out:
anchored.add(rt); print('token over root', rt[:16], 'Verification: OK')
else:
bad('token over root', rt[:16], 'Verification: FAILED')
if not files:
bad('no observation bundle in this directory')
if os.path.exists('root.txt'):
served = open('root.txt', 'rb').read().decode('utf-8', 'replace')
for f, rt, day in roots:
if rt != served:
bad(f, 'the root is not the one in root.txt beside it; the bundle was built on', day)
print('root.txt beside the bundles: every root compared with it')
else:
print('no root.txt beside the bundles: no root is compared with one this record served, '
'and a verified token does not tell whose root it is')
for rt in sorted(set(rt for f, rt, day in roots)):
print('root', rt.strip(), 'built on', sorted(set(str(d) for f, r, d in roots if r == rt)))
if other:
print('files that are not bundles, skipped:', other)
loose = sum(1 for f, rt, day in roots if rt not in anchored)
print('bundles %d; paths reaching the root %d; observations with no digest %d; '
'roots under a verified token %d; bundles with no verified token %d; '
'proofs opening with the root file digest %d; signature %s; failures %d'
% (files, reached, nodigest, len(anchored), loose, otsok, sig, fail))
raise SystemExit(1 if fail else 2 if loose else 0)
EOFCheck the OpenTimestamps attestation
pip install opentimestamps-client==0.7.2
curl -O https://gxplex.com/integrity/root.txt
curl -O https://gxplex.com/integrity/root.txt.ots
ots verify root.txt.otsThis build does not run these lines. They install a client from PyPI, fetch this site over the network, and ask a Bitcoin block explorer for a block header — none of which a build of this site does.
Where each published reason is read from
Every reason the site gives for not tracking a document's text is read from a named field on that document's own record. The reader-facing pages give the reason in plain words; this is the field behind each, so a count can be checked against the store rather than taken on trust.
- text behind a licence
- Read from:
frozen_reason: paywalled_stage_tracked - published only within a listing
- Read from:
frozen_reason: listing_only - no public document
- Read from:
frozen_reason: no_public_document - the site refuses automated requests
- Read from:
frozen_reason: access_blocked - a register, which carries no status
- Read from:
frozen_reason: register_no_status
A shelf this browser keeps
Two controls below write to this browser’s own local storage.
Nothing written there reaches a server: this site serves identical bytes
to every visitor, and there are no accounts and no cookies. The two
scripts this page loads declare no transport of any kind — nowhere
to send, no socket, no tracking pixel and no cookie — and that is a
property scripts/formcheck.py measures on the built artifact
rather than one this page asserts about itself. The client that runs it
is /js/local-shelf.js, and
its contract — the keys, the namespace, the figures and the query
separators — is declared once in
src/lib/localShelf.ts and read by the browser from a block
in this page, so no key is spelled in two places.
Nothing is written until one of those controls is pressed. A visit that presses nothing leaves this device exactly as it found it — no kept page, no figures, and no record that the visit happened.
With scripting off the controls stay hidden and nothing is stored. A control that would do nothing is worse than an absent one: it reports a capability the page does not have.
Kept pages, and a lens over them
The lens narrows the kept list by 2 facets — section of the site, and day it was kept — and the values
under each are derived from the kept items themselves rather than from a
vocabulary this page holds, so a list with nothing in it offers no
chips at all. Each facet is declared once in
src/lib/localShelf.ts and read by the browser out of the
declaration block at the foot of this section, which carries the field
of a kept item its value is read from: the client spells no facet name
of its own, so a facet renamed in one place cannot go on narrowing by a
field nothing carries. The composed query is printed in full beside the
count it is showing and the size of the list it counted over. It is never written
to the address bar: a lens over a list only this browser holds could not
be opened by anyone it was sent to, which is the opposite of the
board’s lens and the reason that one lives in the URL.
Every chip carries a number, and it is derived at each render. The number on a chip is the number of kept pages that would be shown if it were pressed — not how many carry that value. The two differ whenever another chip is already narrowing, and the same function derives both the number and the list, so a chip cannot promise a count the list then fails to deliver. A chip reading 0 stays on the page. It means no kept page carries that value together with what is already narrowing — which is a fact worth seeing, and one a chip that quietly disappeared would have hidden.
Every chip carries a badge naming where its value came from. The question a badge answers is where the value came from — not who thought the facet worth having. This record refuses a facet built from a seed list on the ground that such a facet is a fact about the seed list; the badge is that same finding put where a reader can see it rather than where an auditor can.
- section of the site — this record’s derivation.
A value under it is the first segment of the address, which is a fact about a URL this site publishes. The badge says the value is derived by this record from something it publishes — here, the first segment of an address this site chose for itself, so the word is this record’s and not a publisher’s. The keep control takes it
from
page. - day it was kept — this device.
A value under it is the day the keep control ran, read from this device’s clock. The badge says the value was written on this device by an act of the reader, from this device’s own clock. This record has never seen the value: it is in no build, on no server, and in nothing published here. The keep control takes it
from
device-clock.
The badge is printed once per facet, and it governs every chip under it. Every value a facet offers comes from that facet’s one source, so a badge repeated on each chip would print one fact as many times as there are values. It is repeated in one place that is not visible: each chip’s accessible name carries it, because a reader who reaches a single chip through a list of controls never sees the heading it sits under, and for them a badge printed once is a badge that is not there.
The source named on each line above is what makes the badge a claim rather than a decoration. A facet badged one way whose value the keep control lifted from somewhere else would publish a false provenance in this record’s own voice, and it would read exactly like a true one — which is what a claim nothing holds to always looks like. The binding is data rather than prose, and the lane’s plant refuses a facet that breaks it.
One class has no member here, and that is stated rather than
left silent. Nothing under this lens can be badged a publisher’s word: no facet over a kept list can carry one. A kept item holds an address, the section of this site that address sits in, and the day it was kept — and no publisher printed any of the three. The binding that class would need has no
source at all — null
— so a facet claiming it could not be written. A reader shown two
badges and no third has learned nothing about the missing one, which is
why the absence is published as the fact it is.
The classes offered were two, and this lens needs three. A publisher’s own word, or this record’s derivation — and the day a page was kept is neither. The badge on that chip states the third case in its own words, and this paragraph does not repeat them: two spellings of one statement on one page is a sentence nobody wrote, which is what a seam is. Filing the value under this record’s derivation would be claiming to have produced something this record cannot read. An enumerated set of options is a hypothesis about the world rather than a partition of it, and the branch every menu leaves unstated is that reality returned something else.
Each shown row carries its own account of why it appeared, and every value in that account is read off the row rather than off the term that matched it. The two are equal for a matched row, which is exactly what makes the wrong one safe to write and impossible to notice: a reason taken from the term goes on printing after any change that lets an unmatched row through. With nothing pressed the account is still there and it states the fact — no chip is pressed, so nothing narrowed this list and this row was not selected for any reason — because a row with no account looks like a row whose account failed to render.
Since the figures kept on this device
Every figure below is published elsewhere on this site and the address beside it is where. That is what makes a device-local diff harmless: both sides are public numbers — the ones this build rendered, and a copy of the same ones as they stood on the day they were kept — and nothing about the reader is on either side. A figure that is zero in this build is left out, because a kept zero cannot tell a collection that filled from a build that started reading it.
- 132 tracked entries —
/ - 55 statuses a person has checked —
/ - 301 corrections recorded, document claims and site machinery together —
/how/#corrections-count - 29 published findings —
/findings
What is stored, key by key
gxplex-kept- each address kept from this site, with the page title it carried and the day it was kept. Written by the keep control on Developers, and by nothing else.
gxplex-figures- a copy of the figures this page publishes, as they stood on the day they were kept. Written by the control that keeps the figures on Developers, and by nothing else.
gxplex-density- the row height the board is shown at, as one of two words: compact or comfortable. Written by the compact-rows control on the board, and by nothing else.
gxplex-orientation- one word, recording that the orientation on the front page was dismissed on this device; not when, and not from which visit. Written by the orientation’s dismiss control on the board, and by nothing else.
Forget this device removes every key beginning gxplex-, which is the namespace everything this
site stores shares. It sweeps the namespace rather than a list of keys,
so a key added later is cleared by a control written before it existed.
A key under another namespace is not this site’s to remove and is
left alone.
There is no automatic list of recently viewed pages. The ordinary way to build one writes the current address on every page load, and that is a visit record however local it stays — the first ratified condition on device-local storage is that a passive visit stores nothing, a visit timestamp included. The kept list above is the same list assembled by an act instead: it holds what was kept, and nothing that was merely read.
The fourth condition is carried. The ratification
requires that /how/ carry
one sentence stating the mechanism. That sentence is composed from the
same declaration as the list above, so every key listed here is named
there with the control that writes it and the page that control is on.
Before the correction dated , it named two controls on the board — compact rows, and a list of regulations — of which only the first writes anything in this build, and it did not name this shelf; this
paragraph said so until the sentence changed.
An exact-match index this page carries
The index below is in this page. It is not fetched, and there is nothing to fetch: it is written into the document at build time, so searching it makes no request of any kind — not to this site, and not to anywhere else. Typing in the box moves no bytes.
A query matches a span exactly or it does not match. There is no stemming here, no synonym list, no embedding and no ranking. Each of those would be a claim about what a query meant, and a record that reports what documents say cannot also decide which words are close enough to which others. When nothing matches, nothing is offered: the empty result is the answer, and the nearest row would be a different claim rather than a partial version of that one.
Case is the one normalisation admitted, and it runs as a second pass whose hits are labelled as case-folded where they appear. A normalised read diagnoses why a strict read failed; it never stands in for the strict verdict. A span whose folded form is a different length from its own — a few characters fold that way — reports no folded hit at all, because the offsets would then name the wrong characters.
What is in it, and what each part is cited to
Two populations, and which one a span belongs to is read off the store rather than typed per row. A span sits in the publisher class when the record banked it together with the URL where the publisher said it; every other span is text this record publishes on a page of its own, cited to that page.
- 140 spans cited to a publisher, out of 1191 spans in the index — a span this record banked together with the publisher URL where it was read.
- 1051 spans cited to a page of this site, out of the same 1191 — text this record publishes on a page of its own, cited to that page.
- 198 rows carry those spans, across 5 result types: 132 × a tracked entry, 26 × a subdivision of a tracked entry, 29 × a published finding about this record, 6 × an organisation that produces documents, 5 × an entry of the exact-term index.
- 3 publisher sentences the record holds are not in this index, and the omission is measured rather than silent: each sits beside prose naming where it was read instead of a URL resolving there, so no citation this index could produce would be a publisher’s address.
The class names the citation, never the authorship. A document title is a publisher’s own words printed on a page of this site, and it sits in the page class — not because this record wrote it, but because no stored URL says where the publisher printed it, so the page is the only citation this index can honestly produce for it. Claiming less is the safe direction; the reverse would put a page of this site forward as a publisher’s surface.
Why a result matched
Every result names the field its match was found in, shows the window the match sits inside with the matched characters marked, says whether the match was byte for byte or needed case folded, and prints the address the span is cited to. All of it is derived from the index, and none of it places one result above another: no ordering or score here could support such a sentence.
A shown window is cut to 14 words — the ceiling every quote on this site is held to — and it is centred on what matched rather than taken from the start of the field, so the word that justified showing the span is inside it. Both ends land on whole words. Where the matched text is itself longer than the ceiling the window is withheld with that as its stated reason, because a span cut mid-word is a fabricated measurement of what was written. Where a window elides, it says how many words it is showing out of how many the field holds.
Search
The box is inactive until this page’s script enables it. With scripting off it stays inactive, and nothing on this page is reachable only through it.
Pressing / anywhere outside a text field opens the same index in a panel over the page, and Escape closes it. It is the same index, the same matching and the same result rows — a second surface reading a second index is two answers waiting to disagree.
What this index does not hold. It carries the spans
counted above and, on each entry for a tracked document, a subdivision
of one or a body, the four fields described under “Rights
fields” above, with rights_of naming the entry they
are read for and, where a value is absent,
rights_unresolved saying which absence it is; a
finding’s entry carries none of these, because its headline is this
record’s own sentence. Since 2026-10-24 each entry for a
tracked document or a subdivision of one also carries the evidence state
its row page states, and every entry says why where its type carries
none. Nothing else: no primary source text, no
body of any document, and no page of this site beyond the fields listed
in each result. A publisher’s wording that this record has not banked finds
nothing here, and that is a fact about what has been collected rather
than about what the publisher wrote.
This box is on this page and on /search/. Both carry the one index above
and the one client; the search page adds filters by result type,
citation and evidence state, held in its address after the #. Before the correction dated this note said the box was
on this page only. The older box on the tracker is a different
instrument on a different index and is untouched by any of this.
Last updated: