Writing a client
This page is the artefact. There is no repository to clone, no crate to add, no release to download and no install line — not as an omission, but because a party here needs a key it compiles in and a format it can encode, and both of those are text. What follows is that text. Where a thing you need is not here, this page says so in the same words the design uses, rather than leaving you to discover it against a socket.
You are reading this because you are the person who will implement against us. Nobody else should be here: the human deciding whether to bother is served by the front page, and the agent that will do the work is served by a skill it loads at run time. This page is for the one hour in between.
0.What a client is, in this system
An Ed25519 keypair, a trust anchor you compile in, and an encoder for a handful of packet types. The keypair is the account — it does not authenticate you to an account held elsewhere, because there is nothing else (accounts.the_key_is_CONSTITUTIVE_and_not_merely_the_authenticator). Generating it is one line in any language with a crypto library. Nothing is registered, nothing is issued, and no step of it involves us.
That is the whole of what "zero install" means here, and it is worth being exact about, because the phrase promises more than it delivers: holding a key is not installing software, but implementing a protocol is still implementing a protocol. The design says so in the same key. The friction was never owning the key; it was the encoder, and the encoder is what the rest of this page is for.
1.The anchor, and why it is on a page rather than in a package
The root public key is compiled into the client. Not a flag, not a download, not a first-run prompt — because a client that will fetch its own trust anchor has no trust anchor (deployment.deployment.where_the_root_public_key_comes_from_M248).
That single rule is why this page exists in the shape it does. A package manager would hand you the anchor over the same channel you are using it to authenticate, and a first-run prompt would hand the obligation to whoever is holding the keyboard. The rule wants a human to move the value once, by hand, into source. So the value is published for a human to read:
8d88aca6ad853e3487918925b23460e17c160a996532b40ec6acf7a7293dbf27
Ed25519, generated offline, its private half on no server we run. Provenance is project.the_founder_constants_M335.root_public_key_provenance.
Compare it before you compile it. This copy sits on ferentic.com, which is the domain the anchor authenticates — so on its own it proves nothing. A second copy belongs at a different registrable domain under a different operator, never a subdomain or a path of the first, because a subdomain shares the registrar, the DNS operator and usually the certificate authority.
Second location —
not yet published
While that reads not yet set, you are trusting one channel and should know it. A client may compare the two; it must never require the second at run time.
2.The two objects, and the order is the rule
Before your first packet, fetch two signed objects from ferentic.com and check them in this order. The order has one reason: a document cannot be authenticated by a key it contains (connection.THE_DOCUMENT_CANNOT_BE_AUTHENTICATED_BY_A_KEY_IT_CONTAINS_M351).
First the key list, at /.well-known/ferentic-keys. The only object the root key signs, and therefore the only one checkable by a party holding nothing but the 32 bytes above. It has no parser at all — positional bytes, a count then key-and-window triples, because asking a party to run a parser before it has established anything is the wrong order of trust (connection.WHAT_THE_KEY_LIST_IS_AND_WHAT_IT_REFUSES_TO_CARRY_M353). It carries keys, their windows and its own expiry, and nothing else: no endpoints, no digest of the document, no successor to the root.
Then the document, at /.well-known/ferentic-bootstrap — deliberately not the domain's root, so a machine's trust anchor and a human's page can share one name. It is signed by an operating key the list authorises, inside that key's window, and it carries what you need before one legal packet: the connection endpoints, the protocol version, the category and region registries at their published depths, the schemas with their field indices, and the tariff (connection.connection.the_bootstrap_document). It does not carry the key list.
Both objects carry their expiry in the envelope, covered by the signature and read before any parser runs — not as a payload field you are trusted to check afterwards.
Keep one number across restarts. The highest key-list serial you have ever accepted. A list below it is refused; equal is accepted, because a list is republished only when a key rotates, so every refetch between rotations returns the serial you already hold (connection.THE_ROLLBACK_FLOOR_IS_PARTY_LOCAL_DURABLE_STATE_AND_IT_HAS_A_HOME_NOW_M357). A party with no stored floor has never accepted a list and accepts the first one it verifies. That is what being cold means.
When the list cannot be had, the rule is stated by direction of failure, which is what makes it decidable where it is asked:
Absence falls back. Refusal does not.
- Unreachable, with a cached list that still verifies — proceed, and tell your operator you are running cached rather than fresh.
- Unreachable, nothing cached — stop. Nothing in the document can rescue you, because the list is no longer in it. That is the named price of breaking the circularity, paid deliberately.
- Bytes that arrived and failed — stop, and do not fall back. Falling back there turns a denial into a downgrade onto an older list that may still name a key we removed.
Say which object you could not get. Never one undifferentiated failure. Mirrors we do not control are permitted, and the one that carries only the document is the likely mirror — the key list is new, at a new path, and is the one left behind. A party that cannot tell the two apart reads a partial mirror as "the network is down" and misdiagnoses it completely (deployment.A_MIRROR_THAT_CARRIES_ONE_FILE_IS_THE_LIKELY_MIRROR_M358).
State today. Resolving the name is NOT BUILT, fetching the document is NOT BUILT, and verifying its signature is NOT BUILT — that is the reference client's ledger, not a claim about yours. The anchor is published here because you need it now to compile against. It does not mean the walk completes.
3.The packet
Every packet is big-endian, every multi-byte integer, on the wire and in the preimage (wire.wire.common_header.byte_order_M250). Stated once in the whole design before it was found by writing the codec, which is worth knowing about the sentence you are reading: it is normative because something was built against it.
The common header, four bytes, on every packet:
ver 4 b | type 4 b | flags 8 b | length 16 b - 4 bytes, on every packet
length is the byte count of everything after the header. It is sixteen bits so that a receiver can skip a type it does not implement, which is what makes the ignore-unknown rule work: a receiver ignores fields and keys it does not know, and that is what lets fields be added without breaking you. A receiver discards a packet whose version it does not implement.
The footer, when it is present, is a window and an Ed25519 signature. It is quoted here in the design's own words rather than transcribed, because a width retyped onto a public page is a width that will drift from the one the encoder uses — this page types no constant it can generate. The sentence carries its own working note at the end; that is what the source looks like, and hiding it would make this page a summary of the design instead of a projection of it.
68 B: window u32 | Ed25519 signature 64 B. One footer, shared by every type that signs - the decline, the session report and the proposal. Present iff flags bit 7 `signed` is set, which is why that bit is reserved on every type rather than per type: a footer-skipping layer must work without switching on type. NEW AT A-111: M-222 closed todo S2-25 with this footer and no row was added to this table, so the wire named a thing it did not size.
Present iff flags bit 7 is set, which is why that bit is reserved on every type rather than per type — a footer-skipping layer has to work without switching on the type.
When you sign, and when you must not. On a held connection you never sign: the connection authenticated you at the handshake, and signing again is work nobody reads. On a socketless surface you sign every packet with the envelope footer. That is the whole rule, and it is a property of the surface, not of the message.
The body is typed fields against the published schema, in ordinal order, at the published widths. Those tables are below, and they are the part that actually varies.
Every failure you will meet is typed, and the class is the high nibble (wire.wire.reason_codes.the_class_is_the_high_nibble) — so a client meeting a code it has never seen still knows which of four things happened, which is the part that must never be wrong. Write the switch on the nibble first and the code second, and an unknown code degrades instead of crashing.
The holes are not spare capacity. A code, once assigned, is never renumbered and never reused (wire.wire.reason_codes.never_renumbered), so a gap is a code that was spent and withdrawn, and it stays a gap. Do not allocate into one.
4.The one published branch, and its two schemas
One category is open. Every address outside branch 5 Datasets and data feeds is refused 65 at the door — by design, not by accident, and a supplier whose service fits nowhere published is told exactly that rather than queued.
Its two children are 5.1 corpus and dataset licence and 5.2 continuous data feed. They do not share a schema and one cannot borrow the other's, which is the first thing an encoder gets wrong.
5.2 — continuous data feed
| ordinal | name | width | rule | presence |
|---|---|---|---|---|
| 1 | unit_price | u64 8 | at_most | bit:map.1 |
| 2 | term | u32 4 | within | bit:map.2 |
| 3 | freshness | u32 4 | at_most | bit:map.3 |
| 4 | coverage | u64 8 | within | bit:map.4 |
| 5 | r_train | bool 0 | equal | bit:map.5 |
| 6 | r_redistribute | bool 0 | equal | bit:map.6 |
| 7 | r_derived | bool 0 | equal | bit:map.7 |
| 8 | sample | bool 0 | equal | bit:map.8 |
5.1 — corpus and dataset licence
| ordinal | name | type | width | rule |
|---|---|---|---|---|
| 1 | unit_price | u64 | u64 8 | at_most |
| 2 | records | u64 | u64 8 | within |
| 3 | term | u32 | u32 4 | within |
| 4 | freshness | u32 | u32 4 | at_most |
| 5 | coverage | u64 | u64 8 | subset |
| 6 | r_train | bool | bool 0 | equal |
| 7 | r_redistribute | bool | bool 0 | equal |
| 8 | r_derived | bool | bool 0 | equal |
| 9 | sample | bool | bool 0 | equal |
Declare bounds only on fields the child has. A demander's bound is a possibility and a supplier's body is a commitment, and the two are read differently even where they name the same field.
5.What is not on this page, and why
This is the honest half, and it is here rather than in a footnote because a page that only lists what works is a page you will find out about later.
- There is no reachable endpoint. The pool is not on the internet today. You can write and test an encoder against this page; you cannot complete a handshake against us.
- The per-type packet sizes are not a contract. The design says so itself, and it is right to: those figures are measured estimates. The layouts above are a different thing and are normative — you need the layout, not the size, and the layout is what is published here.
- The reference client is not obtainable, and neither manual's citations into its source tree resolve for you. Treat those as our working notes. The checkable thing is the state word beside each step — works, built-unreachable, not built — which is read from a measured ledger and not from anyone's belief.
- Nobody opens their own account and nobody starts their own verification. Both acts are performed by a person, by hand, on request (
deployment.deployment.THE_LAUNCH_IS_CONCIERGE_AND_THE_DESIGN_SAYS_SO_M317). That is what is true now, not a placeholder for self-service.
Colophon
Prose is authored. Constants are generated. Every key, path, width, ordinal, branch name and status word above is a placeholder that ferentic-manual render fills from design/*.json and writes back followed by its source, under the same grammar and the same check as the party manuals (project.the_client_manuals_M298). The served HTML carries each constant's design key beside it, so the source of this page is the audit of it. Where this page and a design key disagree, the key is right and this page has a defect.
This page loads no script, no font, no image and nothing from a third party — a property the domain serving a trust anchor is required to have, and not a matter of taste.