The public registry
On this page
The registry at jadpo.dev holds public Jadpo packages of both kinds. It is
itself a Jadpo application, deployed to Cloudflare Workers, and calls one
service that isn't Jadpo code: the verifier, the jadpo tool in a
container, which compiles and checks what is published. Private packages
still come from git (Private packages come from git).
Four things the language already does make most attacks on package registries impossible here: nothing in a package runs at install or build, packages have no dependencies, an app commits the exact source it runs, and every host a package reaches is declared. The rules below cover what is left: a hijacked or hostile publisher releasing code that returns wrong values or sends data to a host it may already reach.
Names and publishers
- A package's registry name is
@publisher/name, such as@acme/postcodes; Jadpo's official packages are@jadpo/…. In code it keeps its own name and version label (Postcodes.v2).package.jadponames its publisher, so a fork can't be published as the original. - A name is never reused, after a yank, a takedown or a publisher leaving. A name close to a popular one under another publisher, or to a reserved brand, needs Jadpo's approval.
- A publisher is a person or an organisation. An organisation's owners manage its members and packages; its releasers confirm releases; other members see pending releases. An organisation with two owners needs both owners' passkeys to change its owners.
- Accounts sign in by magic link or passkey, and later with Google or GitHub. Anything that publishes, confirms, cancels, yanks, marks a release urgent or changes members needs a passkey; an email address alone never grants it. Each person can keep several passkeys.
- A package with one releaser says so on its page.
Releasing
A release is made from a tag in any public git repository reached over HTTPS. The registry never accepts an upload, and no publish token exists.
jadpo package publish(or the jadpo.dev page) asks the registry to publish a repository at a tag. It sends only those two, and needs no sign-in, because nothing goes live without a releaser.- The verifier resolves the tag to its commit, fetches that commit, and
then, with no network, checks it: only Jadpo source and the package's
text files (
package.jadpo,provider.jadpo,GUIDE.md,CHANGELOG.md, recordings and translations), within the size limit, and the whole publishing bar. Everything the registry shows about the release is worked out from that source, never from anything the publisher sends. - The release is pending until a releaser confirms it with a passkey, on a page showing its version, surface diff, changelog and facts.
- It goes live, its digest is added to the public log, and its pages are written.
- Routine releases may release themselves. A package can opt into automatic release 24 hours after a release goes pending, unless a member cancels it; every member is told when it goes pending. Only a release that isn't a new major and adds no host, no new place personal data is sent and no new call that changes something qualifies. Any other release always needs the passkey.
- Any releaser can confirm, with their own passkey.
- A new releaser waits seven days before they can confirm a release, and every member is told they were added. Removing a releaser takes effect at once.
- A tag that moves is refused. A tag that later points at another commit is reported to the publisher, and the registry keeps the source of the commit that was confirmed.
- Succession is named in advance. An owner may name a successor, who can claim the package once the owner hasn't signed in for six months, after the owner is emailed and has 30 days to refuse. A package moving to another publisher is shown in every app's next update and restarts the three-day wait below. Without a named successor Jadpo never hands a package to anyone: it can be marked unmaintained, and a fork is published under its own name.
What a release shows
The verifier works out a release's facts from the compiled graph:
| Fact | From |
|---|---|
| Kind, and whether it is pure | The package's folders and what its code does |
| Each host, and the operations that reach it | provider.jadpo and each operation |
| Which calls change something at the provider | Each operation |
| Which personal data reaches which host | personal: marking followed to each operation that sends it (Personal data) |
Whether it declares an erasure: call |
provider.jadpo (Erasure at providers) |
| Types an app may store | Its library types |
| Failures, per operation and function | The surface |
| Confirmed operations, and the last passing contract run | Recordings and scheduled runs |
| The Jadpo releases it compiles and passes its tests with | The compatibility runs |
| Standard types it could use and doesn't | package.standard_candidate |
- Facts and checks, never one score. Each line is a fact or a check that passes or fails. Downloads and stars aren't shown in ranking, and there are no ratings or reviews.
- A written account of the package, by a model, from its source, descriptions and examples: what it is for, and each public function in plain words with the ideas it involves. It is labelled as written by a model, and is checked against the surface and the facts: an account that names a declaration the package doesn't have, or an effect the facts don't show, is written again. A model also reads each release's source diff for what the facts can't show, such as a new literal that looks like an account number, and flags it to Jadpo; it never decides anything.
- The same facts everywhere:
jadpo package checkprints them while the package is written, and the pending release, the package's page,jadpo package showand--jsonshow the same ones. - Reports, not reviews. A security report goes privately to the publisher and Jadpo; suspected malware goes to Jadpo. An app may opt in to telling the registry when its scheduled contract run gets a response a package can no longer decode, sending the shape that failed and never the values. Issues stay in the package's repository.
The changelog
CHANGELOG.md is written from the release's change record. Each entry
names a declaration and what happened to it: added, removed, retyped, a new
failure, a new host, personal data sent somewhere new, or its behaviour
changed. The author writes only why.
- Behaviour the types don't show. The verifier runs the previous
release's examples and tests against the new code. Each one that now
fails is a "behaviour changed" entry. A minor or patch release with one
is refused unless the author marks it a fix and says why, with a line
under the new version's list:
Fix `uk.parse`: why it had to change.
Search
People and agents describe what they need in words, usually without knowing its types, so search reads text first.
- What is searched: each package's written account, its descriptions
and examples, and each public function's own account, by words (for
exact terms such as "H3") and by meaning (embeddings, with
ai.embedand aVectorfield, AI). A result names the function that answers the query. - The standard library is in the same index and comes first, so an agent asking for what Jadpo has built in is told so.
- Shape, when the asker knows it. Each public function also has a
fingerprint of its structure: inputs and outputs reduced to standard
types and their constraints, its failures and its effects.
--fromand--tofilter by it, reading the shapes from the app; the tool sends shapes, never source. - Results are ranked by fit, then by the facts.
Installing and updating from the registry
The rules in Installing safely all apply.
jadpo package add @acme/postcodesinstalls that exact name or stops. It never picks a near match, and it shows the publisher, whether they are verified, the package's age and its facts.- Every digest is checked against the public log, so the registry can't give one app different code from another.
- Releases are available as soon as they are live.
jadpo package update <name>with no version takes the newest release at least three days old, and names the newer ones it skipped.--to <version>takes that release at once, after showing its facts. - Urgent releases skip the wait. A releaser marks a release urgent, with
a reason and a passkey, even under automatic release. The registry
publishes an advisory against the versions it fixes, and
updatetakes it at once and says why. The urgent mark is shown on the release. - Before an update the review (Package upgrades) also shows a new publisher or owner since the installed version, and the change record.
- An app can always fix a package itself: an installed package is in
the format of the app's own integrations, so it can be copied into
shared/providers/and changed there.
Yanks, takedowns and advisories
- A release is never changed or deleted by its publisher. A releaser may yank one: new installs refuse it, and apps that have it keep working. Jadpo removes a release only for malware or a legal order, leaving a record of why.
- An advisory names declarations and versions (
Postcodes.uk.parse, 2.0.0 to 2.1.2), not just a package.jadpo package auditlists the advisories against the app's packages and says whether the app reaches each affected declaration. It is the one check that goes online;jadpo checkandbuildnever do. Run it in CI. - Copies are tracked. Packages copy code instead of depending on each other, so the verifier fingerprints every public declaration's normalised source. An advisory also names the packages carrying a copy, and their publishers are told.
Compatibility with Jadpo releases
Every package is compiled and tested against each Jadpo pre-release, so a
release that would break the ecosystem is caught before it ships, and again
when it ships. A package's page lists the Jadpo releases it works with,
with nothing for its author to keep up. The lowest Jadpo version its
package.jadpo declares is checked against that list. When a release
breaks a package, its publisher is told the errors and what jadpo fix
would change.
Developing for the registry
Publishing should hold no surprises:
jadpo package checkprints the facts, the version the surface diff gives and the change record, exactly as the registry will show them.jadpo package previewserves the package's registry page locally.jadpo package publish --dry-runruns the verifier on the pushed tag's commit and publishes nothing.- The verifier checks with the Jadpo release the package declares and the newest, so a local pass with either is a pass at the registry.
The registry protocol
The protocol is what the jadpo tool and the registry application are each
built against. It is served at https://jadpo.dev/registry/v1/ as JSON,
with failures in the failure envelope, and is versioned by
its path. Reads need no sign-in. The jadpo tool never signs in: nothing
it can send makes a release live.
| Request | Answers |
|---|---|
GET packages/@{publisher}/{name} |
The package: description, kind, code name and label, publisher (verified, official), maintained or its successor, and each release's version, digest, time, and whether it is yanked or urgent |
GET packages/@{publisher}/{name}/releases/{version} |
One release: repository, tag, commit, digest, its surface.jadpo_record, facts, change record, written account, compatible Jadpo releases and its index in the log |
GET archives/{digest} |
The release's files as a tar archive whose files, unpacked, have that digest, as the lock computes it |
GET log/head |
The log's size and root hash |
GET log/proof/{digest}?size= |
The release's log entry, its index and the hashes that prove it is in the log of that size |
GET log/consistency?from=&to= |
The hashes that prove the log of size to extends the log of size from |
GET search?q=&from=&to=&kind= |
Ranked results, the standard library's first, each naming a package and declaration, with why it matched |
POST advisories/query |
Given installed packages and versions, the advisories against them, each naming its declarations, versions and the packages carrying copies |
POST releases |
Given a repository, a tag and dry_run, starts a release (or a dry run) and answers its id |
GET releases/{id} |
Its state (verifying, failed with the bar's rows, pending, cancelled, live), with its facts and change record once checked |
- The log is an append-only Merkle tree of releases, hashed as RFC
6962 does with SHA-256. Each leaf is one line,
@acme/postcodes 2.1.0 sha256:…, so a logged digest can't stand for another package's or version's release.addandupdatekeep the last head they saw in.jadpo/registry_head, asklog/consistencyfrom its size to the new head's and refuse a head the proof doesn't show extends it, then asklog/proofat the new head's size and refuse a release it doesn't show is in the log. Proofs are made by RFC 6962's definitions and checked by RFC 9162's algorithms; the root is never sent with a proof, since a client checks it against the head it holds. - A placeholder fills a whole segment, so
{publisher}is the handle with its@:GET packages/@acme/postcodesispackages/{publisher}/{name}withpublisher@acme. Each part of a scoped name is lower-case letters and digits, starting with a letter, with single-s between them, at most 39 characters. - Every answer's field is always sent,
nullwhere it has no value, as a Jadpo application writes an output; sizes and indexes are JSON numbers. A release's facts are the table in What a release shows, less the Jadpo releases it works with, which are the release's own field since they grow after it is checked. A change record's entry is itsdeclaration, itskind(function,type,provider),change(added,removed,retyped,new_failure,new_host,personal_data_sent,behaviour_changed),detailandwhy. - Its failures are
package_not_found,release_not_found,archive_not_foundandrelease_request_not_found(404),not_in_log(404, also for a release logged after the size asked about),size_past_head(422, a size past the head or sizes the wrong way round) andrepository_refused(422, not a repository reached over HTTPS).POST releasesanswers 202, the others 200. - The lock's
registrysource records the scoped name:package Postcodes.v2 version 2.1.0 registry @acme/postcodes digest sha256:…. - The registry application's served OpenAPI is checked against this
table by a conformance test in the
jadporepository, run against a local registry built from the same table, so neither side drifts. Every row must be served below/registry/v1/with its placeholders, query members, body, status, answer and failures, open to a caller who hasn't signed in, and nothing else butopenapi.json; shapes are compared through any$ref, and what a Jadpo application adds that the table doesn't constrain (the runtime's shared failures,x-jadpo-keywords, anIdempotency-Keyheader) is left alone. The registry application's CI runs it withJADPO_REGISTRY_OPENAPIset to its served document (compiler/runtime §2.1).
The verifier
The verifier is jadpo package verify, the same tool authors run, in a
container image published with each Jadpo release.
jadpo package verify <folder> --previous <folder> --jsonruns the publishing bar and writes everything a release shows: the bar's rows, surface, facts, change record, the fingerprints of every public function and declaration, the previous release's examples and tests run against the new code, and the Jadpo release it was checked with.--serveanswers the registry over HTTPS with a credential the registry holds. Each request names a repository, a commit and the previous release's digest. It fetches over git with network to the git host only, then verifies in a process with no network at all.- It is reached from the registry as an ordinary connection to a provider package, so the registry needs no feature an app doesn't have.
Markdown for agents: /docs/the-public-registry.md.Built from Jadpo main at e00e61f.