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.

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.

byte 0byte 1byte 2byte 3ver4 bitstype4 bitsflags8 bitslength16 bits08162432
The common header, on every packet. Big-endian, and the version and type share the first byte as nibbles. design/wire.json → common_header.layout

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.

ok0x0_0 not assigned1 ACCEPTED12 WON23 SUBSCRIBED34 UNSUBSCRIBED45 WITHDRAWN56 DELIVERED67 ACCEPTED_NO_AUDIENCE78 ACCEPTED_MODIFIED89 PRICED910 SUBSCRIBED_NO_DEMAND1011 SUBSCRIBED_MODIFIED1112 INVITED1213 ALLOWANCE_EXHAUSTED1314 ENTRY_DROPPED_ALLOWANCE1415 CHANNEL_OPENED15loss0x2_32 DISPUTE_UNDECIDABLE3233 VACANT3334 NOT_CLOSEST3435 FILLED3536 EXPIRED3637 WITHDRAWN_BY_DEMANDER3738 GAP3839 DEMAND_EXPIRED3940 NEGOTIATION_DECLINED4041 NEGOTIATION_UNREACHABLE4142 NEGOTIATION_EXPIRED4243 REPORT_WINDOW_CLOSED4344 REPORT_CONTESTED4445 REPORT_OVERRULED4546 DEMAND_CLOSED4647 FRAME_DROPPED47client fault0x4_64 not assigned65 BAD_CATEGORY6566 OVER_BUDGET6667 QUOTA6768 BAD_VERSION6869 MALFORMED6970 UNKNOWN_DEMAND7071 DUPLICATE_CORR_ID7172 not assigned73 TOO_MANY_ENTRIES7374 BAD_REGION7475 STALE_WINDOW7576 BAD_PROOF7677 ONE_QUOTE_PER_DEMAND7778 not assigned79 not assignedserver fault0x6_96 not assigned97 UNAVAILABLE9798 OVERLOADED9899 INTERNAL99100 NOT_ON_THIS_FRAMING100101 not assigned102 not assigned103 not assigned104 not assigned105 not assigned106 not assigned107 not assigned108 not assigned109 not assigned110 not assigned111 not assigned
Every typed failure this protocol can return. The class is the high nibble, so a client meeting a code it has never seen still knows which of four things happened. A gap is a number never yet assigned; a red cell is one that was assigned and withdrawn. Neither is ever reissued. design/wire.json → reason_codes, 46 codes

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

ordinalnamewidthrulepresence
1unit_priceu64 8at_mostbit:map.1
2termu32 4withinbit:map.2
3freshnessu32 4at_mostbit:map.3
4coverageu64 8withinbit:map.4
5r_trainbool 0equalbit:map.5
6r_redistributebool 0equalbit:map.6
7r_derivedbool 0equalbit:map.7
8samplebool 0equalbit:map.8
table:taxonomy.taxonomy.branch_5_2_ordinals_M244.order[cols=ordinal,name,width,rule,presence]

5.1 corpus and dataset licence

ordinalnametypewidthrule
1unit_priceu64u64 8at_most
2recordsu64u64 8within
3termu32u32 4within
4freshnessu32u32 4at_most
5coverageu64u64 8subset
6r_trainboolbool 0equal
7r_redistributeboolbool 0equal
8r_derivedboolbool 0equal
9sampleboolbool 0equal
table:taxonomy.taxonomy.branch_5_1_publishes_at_M261.the_field_set[cols=ordinal,name,type,width,rule]

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.


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.