Where this actually stands

A protocol page that claims to be finished is not telling you anything you can check. This one is deliberately specific about what is measured and what is not. The line between them is one an implementer arrived at while measuring the same number three times: a number that does not predict the next measurement is not a measurement but a report.

Specification Implementations Optional features Open H-items Cross-run
1.8 2 0 6 run 21 Sep

The 5 is the count of numbered open items, and one is larger than the others. The newest, H55, was found by reading: the specification never says what ref is. It appears in §4's table and in N3 — "a ct.load is answered once" and "a refusal is final for that ref" — so two obligations turn on the identity of a thing the document never defines. Nothing says ref is the id of the ct.load being answered, nothing types it, and nothing says how two are compared. My first proposal was to compare ref numerically, as N17 compares a command's id. That was the wrong analogy, and measurement showed it: N17 compares numerically because command ids are ordered — a tie goes to the higher. A ref is never ordered; it is compared only for equality. And the document already has a rule for exact equality: N16 compares v byte for byte. Two kinds of comparison, each with its reason: order → numeric, identity → exact. And the identifier's upper bound of 2^53−1 is not tidiness: above 2^53 JSON loses every other integer — 9,007,199,254,740,993 parses as …992 — so an implementation does not reject such a value, it never sees it, and two different loads arrive indistinguishable. The same reasoning as N12's, unwritten in exactly that one field. The newest is H54: the types of ct.state's fields, and that a relay does not invent. One implementation types every field and drops what fails; the other coerces degraded to a boolean, so the string "ei" marks a client silent and nothing downstream can tell it from a real report. An untyped field forwards rubbish; a coerced field forwards a false value that looks valid. The same item covers one gap that reaches the audio: of seven telemetry fields exactly one makes the other party act — minLeadTicks, which a sender places commands by — and nothing stops it being negative. The gap is now measured on both, and the result is more interesting than the gap: neither places commands by minLeadTicks. On one, because the application is unwritten — on the other, the application is written and places commands constantly, and still never asks. N23 says this is the only telemetry field whose reading reaches the audio; across two implementations it reaches it in neither — and measured, that is about a second: one side's fixed lead is 1920 ticks, 1017 ms, where the other requires zero. The same item also corrected a classification: boundMs is not a number nobody acts on. V4 binds it to the tolerance, and one implementation aggregates it — a single client sending a large value there empties V4's threshold of meaning for everyone. That does not refute the clause, but it moves it to possible, realised in neither — a different thing from one side has not got there yet. H53: what does commontime/1 promise across revisions? An implementer ran their own version 1.4 and version 1.8 side by side against the same messages: in six measured places they disagree, and both carry the same token. In four of them the difference is "the client joins or does not" or "the command is applied or vanishes silently". N16 says the token changes only if the wire format breaks, and none of the six broke it. So the token promises that two implementations can connect; it does not promise that they agree. Which of the two is intended is written nowhere, and two readers read it differently inside one week.

H52 is the rare kind: not a divergence between implementations but a gap inside the text — the ct. prefix is reserved for future versions of the protocol, but N16 says v changes only if the wire format breaks, and naming a core parameter does not break it. A version defining ct.foo as core would still carry commontime/1, and an older client would apply the same command as an application parameter — different audio, with nothing on the wire to separate them. Found while reading H51's rationale. And H51, whether a receiver may reject a param command whose name carries the reserved ct. prefix. The text reserves it as an application's obligation and says nothing about a receiver; one implementation rejects as receiver, the other rejects as issuer — where the error returns to the sender — and accepts as receiver. Three behaviours, and the latter pair is not a contradiction: an issuer has a response channel, a receiver does not (N3). H50 — the shape of a param value, found in 1.7's text right after publication — was approved into version 1.8 together with the ppq keyword. The three before it were numbered late on 19 September, measured on both implementations, and approved the same night into version 1.7. H48: is a message's field set closed — one rejects an unknown field, the other ignores it; N20's sentence about adding a field is empty if the set is closed. H49: a snapshot carrying more than N8's minimum shape — one rejects it as malformed and does not join, the other applies the set and gets N8's winner. And H47, a malformed rampTicks on a command that takes no ramp — one implementation rejects the message because the field has a type in §4, the other ignores it because the text says "ignored". Numbered on the evening of 19 September 2026, both measured; before one of them changed its code, both rejected. Seven were numbered on 19 September 2026 by measuring the code of two implementations, and the owner approved six of them the same evening — H41–H46 — into version 1.5. They were: H40, the reason vocabulary of ct.refuse — one uses four invented strings, the other free text; H41, rampTicks on start and stop — the text says it applies to them, and both implementations independently made it inert there, so the divergence is between the text and the implementations; H42, a tie in minimum RTT — one picks the freshest sample, the other the oldest, two offsets from one sample set; and H43, a non-numeric param under a ramp — one leaves the command unapplied, the other applies it at once; and H44, what an application-level fault in one message does to the connection — the text is silent, and both implementations independently decided the same thing: the connection closes (one measured, one designed); H45, an odd minimum RTT — one carries the half millisecond exactly to the tick, the other rounds the millisecond before the product, and an RTT of 201 ms gives a permanent one-tick difference; and H46, what K7's "identical nominal state" means mid-ramp — two correct formulas differ in the last bit, and the audible ramp is a bit-exact realisation of neither. Each of the six was measured on both implementations before its sentence was written — unlike 1.4, where five of six went in by decision before measurement. H40 was approved later the same evening into version 1.6: N25, the reason of a refusal — five tokens and a free tail, no new field. On 18 September 2026 the owner ratified nine items — H25, H26, H27, H29, H35, H36, H37, H38 and H39 — into version 1.3, and on 19 September the remaining six — H24, H28, H31, H32, H33 and H34 — into version 1.4. The count fell from fifteen to zero in two days.

The second act was different from the first, and the difference is worth saying out loud. Of the first nine, seven had been measured as converging or found by running. Of the second six, only H32 was measured; five went in by decision, and the change log says so. A decision does not make a thing measured: if any of the five turns out wrong in production, it is a finding H40– like any other, and ratification does not protect it. H34's appendix correction from "observed" to "required" is now made — by decision, not by implication, as it should be.

Of the last five findings, three were found by running — two of them in production — and two by reading a fix before it reached production. Both are needed. More findings than that are recorded: an implementer records a finding under their own identifier (F7, F-B2) and proposes it for review, but the H-number is assigned by the specification. Some recorded findings are still waiting for one. Which number is correct depends on which is being asked for, and on this page it is the numbered ones.

Decided

Version 1.8 was approved on 19 September 2026 — the fifth version that evening. It closed H50: a param value is application data, a receiver rejects it only for a non-string name or an absent value key, and a relay passes it through unchanged. And §2 now says with a keyword that ppq MUST be 960 — both implementations used the given value and neither checked, so a third that hard-coded 960 would have disagreed with the clock by a factor of two without anyone noticing. The next is H51.

Version 1.7 was approved on 19 September 2026 — the fourth version that evening. It closed H47–H49: rampTicks is an integer in every command and its value ignored on three; N26 (unknown fields: open on receipt, closed on emission, N11 named as the exception); N8: the snapshot shape is the sender's obligation and a receiver does not reject the surplus. The next is H50.

Version 1.6 was approved on 19 September 2026 — the third version that evening. It closed H40: N25 names the reason of a ct.refuse with five tokens (unavailable, format, length, commensurability, protocol) followed by free text a receiver does not interpret; a ct.load before ct.session is answered protocol, once. No H-items are open; the next is H47.

Version 1.5 was approved on 19 September 2026, the same evening as 1.4. It closed H41–H46: N24 added (a message that cannot be applied), N6, N7 and N14 extended, K7's "identical" given a definition, the ramp removed from start and stop in the command set because both implementations had already done so, and §7 given a second vector with an odd round trip. H40 is open.

Version 1.4 was approved on 19 September 2026. It closed the remaining six items: N20–N23 were added, N11 and N14 extended, ct.state gained boundMs, and V4 now says which bound it refers to. Zero open lasted a few hours: H40–H46 were numbered the same day, each from the code of two implementations.

Version 1.3 was approved on 18 September 2026. It closed nine of the fifteen items raised after 1.2a; the twenty-three raised during drafting had all been addressed before that. The specification has exactly one deliberate gap: the tolerance value that V1 and V4 refer to. It is absent because the measurement that determines it has not been performed, and it will be closed by measurement rather than by discussion.

Measured

The control-plane cross-run has been run — 21 September 2026, 12:44 UTC

Six pass, zero fail, eight without an expected value. The time base works, and these are the project's first real network numbers:

N16 the peer sent commontime/1, byte for byte
N11 21 frames parsed, 0 rejected
N26 no field from outside §4
N24 the peer never closed the connection
N6 20 pings, minRTT 3 ms, offset 5.5–6.5 ms

Every earlier number in this project came from 127.0.0.1. And N26 passing means the peer emits only §4's fields — version 1.7's requirement, measured from the opposite end for the first time.

The control plane never started, and that is the run's finding. The peer never sent ct.session, so no tick could be computed and no command, load or snapshot followed — eight checks had nothing to measure. The cause is measured: no set was open. The peer's code sends ct.session to every listener immediately after the handshake if a session exists, and its log shows zero sessions for that whole boot.

But the run exposed a gap independent of that — H56. Nothing requires a server to send ct.session ever. N22 requires only that it precede loads, commands and snapshots, and that holds vacuously when none is sent. An implementation that never sends it is formally conforming and practically mute — and the peer's relay was in exactly that state at the time of the run, entirely lawfully. Without ct.session a client cannot compute a tick, so every other obligation in the document is unreachable. Proposal: a server MUST send ct.session to a client that has completed the handshake as soon as a session exists — after which its absence means exactly one thing.

The first measured incompatibility

21 September 2026: one implementation ran the other's real payload shape over a real socket. Result: a ct.load whose identifier is the string "ld7" is rejected in the receiver's validator, the answer N3 describes never leaves, the connection correctly stays open (N24) — and the sender waits for a reply that never comes. No material, no slot, no audio, no diagnosis at either end.

Every earlier difference was a reading that showed only when the two were held side by side; this stops material from moving at the first ct.load. It is H55's case, and it changes what that item is: not cosmetic but severing.

Two things the measurement taught beyond the fault. It was found without the network — the implementation generated the other's message shape locally and ran it against itself; the cross-run would have found it in its first second, which is a measure of what not running it has cost. And the gap blocks the diagnosis too: N25's protocol reason means the sender broke a MUST, and nothing types ct.load's identifier — so the sender broke nothing, and the receiver cannot honestly say what happened.

The cross-run tool would have filed a false finding

The tool was written during version 1.2 and had never been run. On its first run it would have reported item H32 as open — it was closed three days ago and is now N22. And on its self-test it reported that the other party had broken N20 by closing after a ct.state: the server closed nothing. The probe closed its own connection at the end of the run, and the check read its own goodbye as the other party's act. The same confusion was in the N24 check.

Had the tool been pointed at the other implementation first, this would have been reported as a finding against it — two normative clauses, both wrong, both the tool's own. The implementer's sentence about it earns its place here: a measurement that cannot fail is not a measurement; one that cannot succeed is worse, because it looks like a finding. Repaired before the first outbound run; self-test 16 pass, 0 fail, 2 without an expected value.

Version 1.8 is in production as of 20 September 2026, 00:10 (H50: two edits, 10 minutes — a nested param value reached the listener verbatim), 1.7 as of 19 September, 23:50 (H47: two edits, 10 minutes), 1.6 as of 23:15 (N25: three edits, 15 minutes) and 1.5 as of 22:44, thirteen minutes after 1.4: seven edits, about 45 minutes including measurement. N14's half-milliseconds, N6's tie rule and N24's check-before-mutation were measured on the client's own source lines; a rejected command no longer burns an id. Version 1.4 reached production at 22:31 — the owner's decision. boundMs cost seven edits and about 40 minutes including the measurement. The first ct.state carries 1000 ms (wide, not degraded, as N20 says), then 1 ms on the LAN and 1–2 ms through the public HTTPS proxy, measured over real ct.pong rounds. The relay reads the field when it is a number and never closes on its absence or wrong type — measured over a socket. N21 is checked at both ends. The other implementation is aligned to 1.8 at 204 tests, and its count of marked guesses is zero for the first time.

The project in one sentence: everything that can be proved by counting has been proved; nothing that can only be proved by listening has been.

The tick arithmetic passes 47 tests with no dependencies, in integers throughout: ct-core pulls in nothing and touches no audio, no network and no browser. The whole suite — ct-core, the client, the clock server and conformance together — was 155 tests on 13 September and is 204 on 20 September, with the rig's calibration another 18 beside it. The measurement rig reads a known 10.0000 ms delay correctly — but only after PHAT weighting was added: unweighted cross-correlation read the same tape as 0.9086 ms, exactly two periods off, with a correlation coefficient of 0.9969 and no warning of any kind. A correct engine holds p95 0.394 ms against a simulated crystal; a concatenating one breaks at p95 12.47 ms and drifts 4.37 ms per hour. A system clock step of 250 ms mid-run threw a drift estimate to −2965 ppm for two minutes when the true figure was +125.

Not measured

Every number that needs an analogue output and a recorder. The accuracy claims above are made against a simulated crystal, which is a calibration and not a measurement, and they are not the tolerance value. Nothing has yet run on two machines across a real network for three hours, which is the length of a set and not a safety margin.

Correction: "blocks" was a prediction, not a measurement

The open-items table used to carry a single column, Blocks, and that was a mistake. The word reported two different quantities as one number:

  • The consequence if implementations differ is a property of the specification, and it can be read from the text.
  • Whether they differ is a property of the implementations, and it cannot be read at all — it has to be run.

Presented as the product of the two, "blocks" is a figure nobody can check. The column is now split, and the measured column says only what has been run.

Measured against two implementations, four items thought to block V4 turned out to be convergent — but two of them more narrowly than was first recorded:

Item What the convergence covers
H25 id The form (a non-negative integer) and the comparison (numeric). Not the upper bound: one requires the 53-bit safe limit, the other only an integer, and they differ from 2⁵³+2 upward. A counter that only increases never reaches it, so no ordinary run can measure the difference.
H26 bpm The number type and the range, without reservation.
H27 late[] Cumulativity and the computation of lateTicks, without reservation.
H35 gain Only gain's zero. The register's heading spoke of a parameter's nominal value in general, but both implementers' evidence concerns gain — a param has no meaningful zero, because the namespace belongs to the application and the value need not be numeric (N15).

Both implementers arrived at the same answer without consulting each other, and for H27 at the same chain of reasoning: one derived the clearing of the list separately, from the same argument. The joining message order (H32) passes when run. The four were ratified on 18 September 2026 with the scopings above.

Measured after 1.3: H26 was binding on one implementation. The answer said 20–300; the code accepted zero — and at zero the tick never advances, and nothing says so. A matching answer is not matching code; that is why every item here has two rows, specification and measured.

See The ask for what actually predicts divergence — it is not the size of the consequence.

Why this page says H30 and not H28

H-numbers are assigned by the specification, but the implementers cannot see the register. Two implementers therefore propose the same number for different things — which happened twice in a row. The value of v was proposed as H28, but it is H30; the meaning of minLeadTicks as H29, and it is H34. The specification's own H28 and H29 are different matters: ct.state cannot report the bound §6 refers to, and the smallest message size an implementation must accept.

The collision is nobody's carelessness. It follows from decisions passing through an intermediary, and it is this page's most measurable example of what that intermediary costs.

Interoperability no longer waits for hardware

This is the most recent change to the plans, and it separates the interoperability gate from the hardware gate.

A client that refuses every load — ct.refuse with reason missing — and stays silent under V3 is fully conforming. A cross-run against such a client proves what a schema cannot: that two state machines agree about the handshake, the joining order and the application of a snapshot.

It needs no analogue output, no recorder and not one audio file — and it can be run the same day H30 is decided, which is before a two-input interface has even been ordered. Audio is a different gate and different work.

The cross-run stopped at the first message, and that was the gate working

Two independent implementations exist. The second was written from the specification text alone, by an author who has never seen the first implementation's code. Their cross-run does not get past the first message: N16 requires closing the connection when the v values differ, but the specification never stated what the value is. One sent commontime/1, the other commontime/1.2, and the connection closed correctly in both directions.

Neither author changed their value to match the other. That refusal is the point of the exercise: a value agreed between two implementers is a private convention that no third party could reproduce. It belongs in the specification before it belongs in anybody's source file.

The item was recorded as H30, and for a long time it was the only measured blocker.

H30 was decided on 13 September 2026. The value is commontime/1. It is a compatibility token, not a document version — it changes if and only if the wire breaks, and 1.1, 1.2, 1.2a and 1.3 do not change it. The comparison is byte for byte, not parsed as a version number and not normalised. On a mismatch the connection closes with code 4001; a generic 1000 is indistinguishable from a normal close, and the symptom would then be "nothing happens". Version 1.3 is the rule's first evidence: it changed nine items and not the value of v, because the wire did not break.

The reasoning rests on N16's own logic rather than on either implementer's value. N16 forbids version negotiation and requires closing on mismatch, so if v carried the document version, every editorial correction would sever every running connection. Version 1.2a was a non-normative fix to a single sentence of rationale. And the specification had been showing the answer at the top of every page without stating it: commontime/1 · version 1.2 · approved 13 September 2026 already separates the token from the version typographically.

What the decision changed, and what it did not. One word will not do, because these are two different quantities — the same distinction H25 and H26 taught, now applied to the outcome of their own resolution:

Cross-run
Specification does not block it — N16's gap is closed
Measured not yet run — both implementations now send commontime/1, but the cross-run has not been executed

The decision changed the upper row, not the lower one. The control-plane cross-run is clear as far as the specification goes. B adopted the value, measured from code on 18 September 2026; the same day the owner decided the process for the conformance suite, and the run is authorised. The lower row changes when the run has been run, not before. Audio remains a separate gate after that.

The decision was verified against an implementation, not merely recorded: the value (including that it is not 1.1, 1.2 or 1.2a), the byte comparison against nine near misses — a space at either end, a newline, case twice, commontime/1.0 read as a version number, commontime/10, commontime as a prefix, and B's commontime/1.2 — and close code 4001, together with its falling in the application range 4000–4999. All three as tests.