What it cannot prove
This is a section of the product, not a disclaimer at the foot of it. A reviewer who knows this field should find nothing here that we did not say first.
Version 30, 2026-10-10
The claim, so the limits have something to be limits of
TimeWitness says: at this local counter reading, UTC was somewhere in this interval, and here is the signed evidence this receipt carries, in up to three roles, from parties who have never heard of us, each part showing one thing about the time and none saying when that reading was taken. The interval is our own claim and the outside evidence does not vouch for it. That evidence is checked, and on the receipt committed in this repository it leaves one edge and not two: the beacon round shows the receipt was made no earlier than that round, and the timestamp authority states no accuracy of its own, so nothing here puts a number on how wrong that authority's clock could be. A bound resting on that evidence is what we are building towards. Bounded time and unbroken order. Not accurate time.
About time itself
It cannot prove exact UTC over the public internet.
The design's bound is milliseconds and today's is about a sixth of a second on a machine that reaches the sources that narrow it. A machine that reaches only Roughtime is refused rather than given a wider bound, and the section on what is built says why. The 5 to 50 ms, the 1 ms and the 100 microsecond figures are quoted from public research and none of them has been measured by us. They belong, in that order, to the public internet with no hardware of our own, to a clock updated once a minute, as primary reference clocks are, and to a cloud instance with a hypervisor clock. The first two are read from David Mills' Executive Summary: Computer Network Time Synchronization, which puts an internet path at a few milliseconds to several tens of milliseconds and a clock updated once a minute, the interval used with primary reference clocks, at the order of a millisecond. The third is from Amazon's post of November 2023 on microsecond clocks for EC2 instances, which gives it as the clock error bound a supported instance typically sees over NTP. A claim needing microsecond truth needs hardware this product does not sell. Nanoseconds is the resolution of the local read and never the accuracy to UTC; the two get confused constantly and that confusion is the problem this product exists to fix.
It cannot prove a figure that was measured somewhere else.
Every number above carries the conditions it was measured under and says whose it is. A figure quoted without them is not a figure, and a figure taken from somebody else's research and presented as ours is worse than no figure.
It cannot prove elapsed time from a verifiable delay function.
Such a function proves sequential work, which is a different thing.
It cannot prove that a machine stayed alive and honest through a power cut or a network outage.
It can record the gap. A bound says nothing across a suspend, because the machine has no idea how long it was away.
It cannot carry a bound far past its last synchronisation.
The machine's own oscillator changes rate as it warms and cools, and nothing on the machine can see that happening. So the interval widens as the gap grows, and past the point where it is too wide to be worth anything the agent refuses rather than reporting a large number. On the shipped settings that point is about sixteen minutes after the last time the sources answered. That figure and the widths behind it, 26.6 ms at the moment of synchronising and 234.6 ms at fifteen minutes, come from the simulated harness at crates/clock/tests/common/mod.rs, whose whole purpose is that the true offset is a number the test wrote down; they are the arithmetic of the model rather than a reading from a real network. An agent that cannot reach its sources stops issuing receipts; it does not issue worse ones.
It cannot prove that this machine's oscillator is the one the arithmetic assumes.
Every allowance the model derives for the oscillator rests on one assumption: that the rate of this machine's counter stays inside the band the policy states for it, a hundred parts per million from one end of the band to the other at the shipped settings, so the largest magnitude a part may honestly show is fifty. Both figures are Policy::frequency_span_ppm in this tree. They come from a consumer crystal's published specification across its temperature range, they are a choice about hardware rather than a measurement, and nothing in this product has measured either on real hardware. What the agent does do, from 2026-09-18, is read its own fitted rate back against that band every time it fits one. Where the fit puts the machine outside the band and is sharp enough to tell one rate in the band from another, the model stops claiming the rate and widens by the magnitude it measured instead, so the interval holds what was seen rather than what was assumed. Where the fit is not that sharp, and that means an error bar wider than the whole band, it has measured nothing about this counter, so neither the magnitude nor the error bar is carried and the widening is half the band, exactly as it is before any fit at all. That is what every start produces, because the first rounds are a fraction of a second apart and a rate fitted across a fraction of a second is the sources' own scatter divided by almost nothing. So what it cannot do is see a machine outside the band until there is a fit sharp enough to say so, and there is none in the first rounds after a start. The read-back is blind in the same way wherever a fit's error bar is wider than the band, whatever the agent's uptime: it answers that it cannot tell where this machine's rate sits, the agent prints that answer rather than an inside reading, and a machine outside the band goes unseen for as long as the fit stays that blunt. On a machine outside the band, nothing on this page is a promise the arithmetic can keep.
About the evidence, and this is where most of the surprises are
An authenticated corridor does not tighten the bound.
A Roughtime server states a midpoint and a radius in whole seconds, and the three public servers reachable on 2026-09-07 were stating one, three and five. So a corridor is seconds wide. It puts a signed interval round the moment its server answered, which a stranger can check and which the receipt's interval has to overlap, and the millisecond figure comes from ordinary time sources rather than from it. Plain NTP and NTS run in every stamp beside the corridor, and neither signs anything a stranger can check. Anybody who expects Roughtime to be the precise part has the roles the wrong way round.
A corridor does not prove the server's clock was right.
It proves that the holder of a named key signed a statement covering a nonce we chose. The draft says so itself. A server whose clock is wrong produces a perfectly valid response saying something false, which is why more than one is used and why the corridor is only one of the three roles.
More than one source is a limit and not a guarantee.
What using several sources buys is exactly this: while fewer than half of them are wrong, the reported interval holds true UTC, and no source that disagrees with the others can narrow it, widen it, or move it. That is the whole of the protection and it stops at the edges. Half of them wrong is not covered. A source list whose names resolve to one operator is covered from 2026-09-09 and was not before: the selection counts operators rather than names and refuses a majority resting on too few of them, and what it still cannot see is two operators sharing an upstream, a path, a constellation or a server program. A source in the minority can still move the reading inside the interval a majority allows, because the reading is a weighted summary and the interval is the claim.
A source that agrees with everything is refused, and that is our own rule.
A server stating a radius of an hour contradicts nobody, so it is never in the minority and the sentence above is not about it. Two servers ten seconds apart at a radius of three seconds agree nowhere, and the agent refuses. Add the hour-wide server and Marzullo's algorithm finds a majority, because two of the three now allow every point between one server's floor and the other's ceiling, and it signs an interval sixteen seconds wide where neither honest server supports more than six. That interval does hold true UTC while at most one of the three is wrong, which is what the algorithm promises, so nothing signed there is false. This agent refuses it anyway, decided 2026-09-09. A source whose interval contains every other interval in the round cannot be put in the minority by any answer the others could have given, so its agreement costs it nothing; where setting those sources aside leaves the rest without a majority of their own, nothing corroborated anything and the agent declines to sign. The rule only ever turns an answer into a refusal. It never moves an interval and it never accepts anything the published algorithm refuses, so the guarantee here is stricter than the standard rather than different from it. The code is crates/clock/src/marzullo.rs and it states the departure at the head of the file.
And a source that agrees with everything no longer decides who else has lied.
The rule above is about a source that manufactures a majority on its own, and three sources can do between them what none of them does alone. Three Roughtime servers state a radius of a second each and every NTP interval fits inside all three of them, so none of them contains the other two and none of them was set aside; all three then took part in deciding which of the narrow sources was in the minority. They could not have disagreed with any of them. Measured on 2026-09-11 in `crates/clock/tests/two_kinds_of_source.rs`, which runs on the simulated harness rather than on a real path, so every figure in this item and the two below it is the arithmetic of the model and not a reading from the wire: three honest NTP servers throw out a liar whose interval sits two hundred milliseconds off them, and adding three Roughtime servers kept him, taking the interval from 12.52 ms to 223.52 ms wide and moving the reading 82.570909 ms inside that interval. Nothing signed in either round was untrue and both held UTC. What was wrong is that three faults were tolerated where none of the three had been earned.
What that second rule still does not cover, and it is a figure rather than a caveat.
How much room the width allows for a fault is still counted over every source that answered, so a source that could not have disagreed still buys the interval some room: 34.52 ms of width in the round above against the 12.52 ms of width the narrow sources reach on their own. Counting that over the sources that could have disagreed is the same arithmetic pointed at the width, and it takes an ordinary honest round of four agreeing servers down to the narrowest of them with no fault tolerance left, so it is a decision about the claim rather than a defect to fix. It is still open.
A source that does real work can still be the broken one.
The second rule is about a source that could not have disagreed with anybody, and a source can be informative and still be the one that is wrong. A server that overlaps two others which contradict each other, without containing either, is doing real work: it excludes everything outside itself, so it counts, and it can still be the broken one. Half or more of the sources being wrong is not covered by any of this. A source list whose names resolve to one operator is covered from 2026-09-09, by the operator count rather than by this rule, and the two are separate tests that refuse separately.
A source is not taken at its word about how certain it is.
Two of the four timestamps in an exchange are the source's, and so is its statement about its own accuracy. A server willing to say it spent the whole round trip thinking and that it knows its own time exactly is describing a point rather than an interval, which is why no source's answer is taken narrower than the policy floor and why a reply whose own two timestamps cannot both be true is dropped rather than reduced to something usable.
A beacon's signature does not cover the time.
A drand round signs the round number and nothing else. The moment that round belongs to is arithmetic on the chain's published genesis and period, which a verifier holds as part of the chain definition. A verifier that disagrees about the genesis gets a different answer and no signature will tell it so.
A beacon rests on a coalition not existing.
The chain used signs each round independently of the one before it, so a group holding enough of the shared key could compute any future round today and arbitrarily far ahead. Not-earlier-than therefore rests on that group not existing, not on mathematics.
A beacon pins the receipt, not the thing being stamped.
A receipt containing a value published at three o'clock was finished after three o'clock. The file it is about can be from any time at all.
A final witness attests the payload, not the receipt.
The timestamp authority is shown the hash of what is being stamped, before the receipt exists, because a receipt cannot contain a token that covers itself. So the not-later-than edge is about the payload rather than about the receipt as a whole.
A final witness does not prove its own clock either, and on the two authorities used it bounds nothing at all.
It is that authority's word, signed. Neither of the two authorities used states any accuracy, which is not a claim of perfection: it means neither puts a number on its own error. So neither token bounds the moment in UTC, however good its signature is, and the verifier says so rather than computing an edge. Until 2026-09-19 it did compute one, reading the absent field as a stated nought, which is the narrowest the token could possibly be read. A reader who has read an authority's published practice can say what they allow for that authority's clock, in their own anchors, and the figure is then printed as theirs. Nothing that ships carries one.
A timestamp token is checked against a pinned certificate, not a chain to a root.
What this code establishes is that a token was signed by the key in a certificate chosen in advance. It does not walk a chain to a commercial root, check revocation, or check the timestamping extended key usage. That is a narrower statement than trusted, and it is the honest one for what the code does.
Where several parties who are meant to be independent collude, a coherent false history can be manufactured.
We do not claim to prevent that at runtime.
About what is being stamped
It cannot prove that a photograph, a document or a recording is real.
It binds a hash to a bounded time. What the hash is of is somebody else's problem.
It cannot prove that the software describing an event described it truthfully.
If the thing calling the agent has been compromised, a correct receipt attests a false statement, precisely and verifiably.
It cannot prove intent.
It cannot show that a person meant to do the thing that was stamped, without a confirmation step outside this product.
It cannot prove anything about a time before the agent was installed.
It cannot prove that something did not happen.
Absence needs a defined universe of events and authenticated proof that ingestion was complete, and this product has neither.
About what it does when something is wrong
It does not prevent anything.
A refusal records that TimeWitness declined to sign, and today that record is a return value inside the agent rather than anything a third party can be shown. It does not record an action being stopped, and there is no enforcement path in this design. A clock rollback is the same: it is detected and recorded after the event, and nothing it enabled is undone.
It does not establish legal weight.
Legal standing in this area comes from accreditation, meaning qualified trust service provider status under eIDAS, and not from engineering. A private root is admissible and never presumed. No regulation we have checked, including AI Act Article 12, SEC 17a-4 and FINRA 4511 and 6820, requires cryptographic proof. Two of them ask for more than a log: FINRA Rule 6820 sets a tolerance to which a member firm's business clocks must be synchronised with NIST time, and SEC 17a-4 asks for records kept where they cannot be rewritten or with a time-stamped audit trail. TimeWitness claims no part in meeting either, so a compliance claim built on any of these rules would be false. The timestamp authorities used here are free services and none of them is a qualified trust service.
About what is built today, which is the part that moves
A bound is about 155 ms where a machine reaches the sources that narrow it, measured 2026-09-09 at sixteen polling rounds on an ordinary desktop, and a machine that reaches only Roughtime is refused, where before the operator floor it got seconds.
Three time source clients exist in this repository, Roughtime, plain NTP and NTS, and only Roughtime signs anything a stranger can check, so the one that can be shown to a stranger is the one that cannot narrow the bound. A Roughtime server states its own uncertainty as a radius in whole seconds, so a bound resting on Roughtime alone would be seconds wide whatever else is done to it, and the shipped floor refuses such a round; a plain NTP server states a delay and a dispersion in units of about fifteen microseconds and signs nothing at all. Measured against Roughtime alone on 2026-09-08, every one of them at the four rounds the Action shipped that day: a bound of 16.219 s from a GitHub runner, a bound of 16.424 s on an ordinary desktop's receipt committed at that path then and since replaced, and a bound of 16.439 s from that same desktop, stamped at 08:08 UTC. Measured against two kinds on 2026-09-09 at the sixteen rounds the Action ships now, on an ordinary desktop: 153.6 ms and 164.8 ms wide over the first two of three passes at 11:52 UTC, and a bound of 176.7 ms on that desktop's receipt committed at that path then and since replaced, taken at 12:05 UTC. At 12:09 UTC a GitHub runner reached a bound of 211.3 ms, and that desktop reached the same on the remaining one of its three. Every one of those is our own measurement on the machine it names, and none of them is a figure for anybody else's machine. Measured with all three kinds on 2026-09-09 at the sixteen rounds the Action ships: 154.1 ms, 159.2 ms and 154.7 ms wide over three passes on an ordinary desktop at 17:28 UTC, and a bound of 149.8 ms on the receipt committed at that path then and since replaced, taken at 17:32 UTC, nine servers answering and nine kept. Measured again with the independence rule in, on the same desktop at the same sixteen rounds: 149.3 ms, 161.2 ms and 163.2 ms wide over three passes at 18:39 UTC, and a bound of 153.9 ms on the receipt committed in this repository at 21:41 on that desktop's clock, 18:41 UTC, nine servers behind six operators and nine kept. Enforcing independence narrowed nothing and was never going to, because the rule refuses rounds rather than narrowing them: the sources overlapping is 35.1 ms of half width on that receipt against 34.9 ms on the one before it. Measured through the resident agent on the same desktop on 2026-09-18, after the ageing of a source's interval over the local counter was bounded by the band, at thirty-six minutes of uptime and a thirty-two second polling cadence: 130.346 ms, 115.308 ms and 115.260 ms wide over three readings at 20:32 UTC, nine servers behind six operators and nine kept, with the sources overlapping at 37.991 ms of half width on the first and 36.830 ms on the other two. Measured through the resident agent with the independence rule in, on the same desktop on 2026-09-09, at the same uptime and the same cadence: 128.7 ms, 128.8 ms and 129.1 ms wide over three readings at 19:17 UTC, against 122.7 ms, 122.8 ms and 122.6 ms wide from the same agent at the same uptime and the same cadence before the rule at 18:04 UTC. The sources overlapping is 38.4 ms of half width there against 37.6 ms before the rule, so the six milliseconds between the two sets is a public network an hour apart rather than anything the rule did. The third kind narrowed nothing and the breakdown says so: the sources overlapping is 34.9 ms of half width on that nine-source receipt against 34.5 ms on the six-source one taken at 12:05 UTC, and what moved between the two receipts is the oscillator, 0.5 ms against 17.3 ms, which is how long after the last exchange each stamp was taken. Measured through the resident agent with all three kinds on 2026-09-09, at thirty-six minutes of uptime and a thirty-two second polling cadence: 122.7 ms, 122.8 ms and 122.6 ms wide over three readings at 18:04 UTC, against 133.5 ms, 136.8 ms and 128.9 ms wide from the same agent at the same uptime and the same cadence with two kinds at 13:27 UTC. The sources overlapping did not move there either, 37.6 ms of half width against 37.1 to 38.3 ms, so the ten milliseconds between the two sets is the fit and the oscillator rather than the sources. Measured through the resident agent on the same desktop on 2026-09-09, at thirty-six minutes of uptime and a thirty-two second polling cadence: 133.5 ms, 136.8 ms and 128.9 ms wide over three readings at 13:27 UTC, against 161.1 ms, 159.4 ms and 162.2 ms wide from one-shot runs on that machine twelve minutes earlier. What the agent moved is one term: the model's own residual fell from 39.3 to 45.6 ms of half width on those one-shot runs to 24.3 to 25.0 ms, and the sources overlapping did not move at all, so on that machine the width is now set by the sources rather than by the fit. Of the 153.875 ms of width on the receipt committed in this repository, the largest single part is the model's own regression residual doubled by the coverage factor, 38.011 ms of half width, with 35.081 ms of the sources overlapping and 3.586 ms of the oscillator beside it, all four read 2026-09-10 off the verify command run on that receipt, which prints half widths. Polling more times does help and it is the shipped default that was wrong about which way: below three rounds no line is fitted, so the scatter of the measurements is never measured and never enters the width, and the bound at one round is narrower because less was measured rather than because the clock is better known. Measured on this desktop at 09:54 and 09:56 UTC on 2026-09-08 against Roughtime alone, two passes at each setting: 6.2 s wide at one round, 17.4 s wide at three, 16.4 s wide at four, 12.0 s wide at sixteen and 10.4 s wide at thirty-two. So the figure this product leads with is its own bound, about 155 ms at sixteen rounds where the sources that narrow it are reachable. Where only Roughtime is reachable it now refuses, and the 12 s of width it reached there on 2026-09-08 is from before the operator floor. About one second is still the target and it is a target for something else: a bound resting on third-party evidence rather than on the agent's own model, which nothing outside the tests constructs, and which a timestamp authority writing whole seconds puts a floor under. That is the rule underneath it, and it holds for everything this product says about itself: the prose may be forward-looking and a number may not, because a reader reproduces a number and cannot reproduce a plan.
The agent starts at boot only where somebody has installed it as a service, and a restart has been checked on Linux and nowhere else.
timewitness agent install hands the agent to a systemd unit on Linux, a launchd daemon on macOS or a scheduled task at boot on Windows, and timewitness agent uninstall takes it away again. Run by hand, timewitness agent is a foreground process that runs until it is stopped, so a machine that reboots has no agent until something starts it. On 2026-09-25 a Linux virtual machine with the service installed was restarted, and the agent was answering afterwards with nobody having started it. The machine was built for the check and thrown away after it. On macOS and Windows the install and the uninstall have been checked and a restart has not, so there the agent coming back after a reboot is built and not shown. On Linux the unit takes away the agent's permission to set the clock. On macOS only root may set it and the daemon does not run as root. Windows has no such switch, so there the promise that the agent leaves the clock alone rests on the code. Only Linux has a released binary, from v0.7, so on Linux the service can be installed from that binary, and on macOS and Windows it is installed from a command line somebody compiled.
Leaving the agent running longer stops narrowing the bound after about thirty minutes.
The model's own residual is the largest single part of the bound on the agent's path and it falls as synchronisations pile up, so a reader is likely to assume that an agent left up all day reaches a narrower number. It does not. Policy::regression_window is 1800 seconds and the model drops every point older than that before history_capacity binds, so at the shipped thirty-two second cadence the fit saturates at about fifty-six points after half an hour and never has more. Measured 2026-09-10 through the model's own simulated harness at that cadence, which is the arithmetic of the model and not a reading from a real path: the residual falls from 13.522 ms of half width at five minutes to 5.802 ms at the window, and is then 5.752 ms at forty minutes, at an hour, at ninety minutes and at two hours, the same number to the nanosecond. Measured the same day on an ordinary desktop through a resident agent against the nine published servers, one reading every three minutes: 47.704 ms of half width at five minutes of uptime, 22.262 ms at thirty-two minutes, and no lower after that. Whether thirty minutes is the right window is an open question about the regression rather than a setting anybody can change from outside.
The one line a workflow installs runs the one-shot command and not the agent, so every receipt this product has issued in continuous integration came from a model built and thrown away in the same job.
action.yml runs timewitness stamp with a polling round count and no --agent. A build runner is a machine that has existed for ninety seconds, so there is nothing for an agent to be resident on, and the answer to that is not a longer baseline: it is a bound resting on third-party evidence.
A fresh agent refuses most readings for its first two minutes.
The four settling rounds a fraction of a second apart buy the model a line to fit and nothing more. A reading is extrapolated over however long ago the last round was, so once the agent settles into its thirty-two second cadence the width sweeps up across each gap, and a fit whose baseline is under a second is being asked to reach thirty. The residual that comes out of that is far past the shipped 250 ms ceiling, and the agent refuses rather than signing an interval wider than the one it said it would sign. Measured 2026-09-18 on an ordinary Windows desktop against the nine published servers, at the shipped thirty-two second cadence and 250 ms ceiling, on two agents started in the same minute: one asked every five seconds for forty minutes, where the last refusal was at 102 s of uptime and 13 of the first 34 readings were refused, and one asked every ten seconds for seven minutes, where the last refusal was at 101 s and 8 of the first 17 were refused. It can sign once in its first seconds and then stop, which is worse than not signing at all for somebody who reads the first answer as the settled one: the first of those two signed at 3 s of uptime, right after the settling rounds, refused every reading from 9 s to 37 s, signed once at 43 s and refused again from 48 s to 67 s. What the settling rounds buy is a fit that exists, and not a fit worth signing. Measured the same way on the same machine on 2026-09-10, one reading every five seconds for twelve minutes, twice, the last refusal was at 166 s of uptime on one run and 168 s on the other, with 25 and 24 of the first 34 readings refused; the ageing of a source's interval changed on 2026-09-18, and the figures from that day are the ones that describe the code that ships. From 2026-09-24 timewitness status states the width, and says in a plain sentence how wrong the clock could be, within 60 seconds of a fresh agent starting on a machine that can reach its sources, whether or not a stamp would be signed at that width, and it says which. The width it prints while a stamp would be refused is the width the agent refused to sign, so nothing was narrowed to reach that time. Measured 2026-09-24 on an ordinary Windows desktop against the nine published servers, at the shipped thirty-two second cadence and 250 ms ceiling, over ten starts from cold one after another with other builds running on the same machine and an agent asked twice a second for five minutes of uptime per start: the first width and sentence came at 1.9 s to 6.6 s of uptime, and a width stated while a stamp would have been refused was 250.007 ms wide at the narrowest and 4.873 s wide at the widest. On the same desktop the same afternoon, twenty more starts in two runs of ten had the first width and sentence by 8.3 s of uptime on nineteen and at 38.9 s on one, where the model refused all four settling rounds because the sources that could have disagreed did not agree with each other, and the first bound came with the next round thirty-two seconds later. That is why the time stated is a minute rather than the few seconds most starts take. Measured over those ten starts on the same desktop, 1224 of 1809 answers in the first two minutes of uptime were refused, 41 of them before the agent had a bound at all, and 38 of 1810 from three minutes to five, 25 of the 38 in one start, and four of the ten agents refused at least once in their last fifteen seconds, so a refusal becomes occasional after about three minutes of uptime and never stops altogether.
After it settles the agent still refuses a reading whenever the bound crosses the ceiling, and the share is not a fixed number.
The shipped cadence is thirty-two seconds and the shipped ceiling is 250 ms, so the width sweeps up across each polling gap and where the top of that sweep lands is what decides whether a reading is signed. On an ordinary desktop the top of it sits close enough to the ceiling that the answer moves with the network. Measured 2026-09-18 on an ordinary Windows desktop at those settings, one reading every five seconds: nought refused of 101 readings from three to twelve minutes of uptime, at widths of 130.988 to 203.677 ms, and nought of 23 readings ten seconds apart from three to seven minutes on a second agent started in the same minute, at widths of 149.224 to 197.686 ms. Measured 2026-09-10 on the same machine, before the ageing of a source's interval changed, one reading every five seconds: nought refused of 100 readings from three to twelve minutes of uptime on one run and nought of 103 on a second, at widths of 144.636 to 238.409 ms and widths of 140.933 to 217.723 ms, and one refused of forty readings five seconds apart at five to eight minutes, at 254.047 ms of width against the 250 ms ceiling. So the share on one machine, measured on 2026-09-10 and 2026-09-18, is somewhere between nought and one in forty, and a deployment should expect a refusal now and then rather than never. The refusal is the design working rather than a fault: a wider interval says something true and a narrow wrong one does not, and the last good reading is never offered.
The agent answers sixty-four callers at once and refuses the sixty-fifth.
A cap is a refusal, so it is on this list with the rest of them. Every caller gets a thread of its own from 2026-09-10, and past CALLERS_AT_ONCE a caller is turned away in words rather than queued behind the others, because a queue behind a full cap is the same unavailability moved somewhere the caller cannot see it. Before that date the agent answered one caller at a time and its ceiling on waiting for a token before it looked at one was two seconds, so two sockets opened and left silent stopped the machine issuing receipts at all: measured on an ordinary Windows desktop, a legitimate ask took 56 ms alone and 40158 ms behind twenty of them. What the cap does not do is make the agent proof against somebody who already runs code on this machine. It turns a cheap permanent outage into an expensive temporary one: holding all sixty-four now costs a fresh connection every half second rather than two sockets opened once, and the agent stays up, answers the moment a slot frees, and says what happened. Somebody who can run code as this user has a worse move available anyway, which is to write the endpoint file.
Three protocol implementations stand behind the nine servers a round asks, so at most three of the nine can fail for a different reason in the code.
SourceKind names plain NTP, NTS, Roughtime and local hardware, and three of the four have a client, crates/sources/src/roughtime.rs, crates/sources/src/ntp.rs and crates/sources/src/nts.rs. What runs is three servers of each of those three kinds. Local hardware has no client and needs a receiver this product cannot assume anybody has. The README says four to six independent sources, and independent in this product means the operator rather than the protocol, which is the only reading the design supports: SourceKind has four variants, so six kinds cannot exist and a claim of four to six kinds could never be met. On the count this product defines and enforces the claim is met, at six operators with the shipped floor refusing below four. A count of protocols is a separate limitation and it is this one.
A defect in one of the three source programs is one fault across three of the nine names at once, and no count of operators can see it.
Three servers of each kind means three servers running the same protocol against the same client code here. Where the fault is in this repository it reaches every source of that kind whoever runs them, and where it is in a widely deployed server program it reaches every operator running it. The operator floor counts parties and a shared implementation is not a party, so the two limitations sit beside each other rather than one covering the other. What that leaves is three chances to be wrong in code against six chances to be wrong in the world.
The nine servers a round asks stand behind six operators, and from 2026-09-09 the selection counts operators rather than names.
Cloudflare, PTB and Netnod each answer on two of the three protocols, so three of the nine names were being counted twice. The selection rule discards a source whose interval does not overlap the majority, and that guarantee holds only where fewer than half the sources are wrong, which needs them to be wrong separately. Sources under one operator fail together and lie together, so a count of names is not the count the guarantee rests on. Three things now enforce that rather than describe it: a source carries an operator, a majority of intervals resting on a minority of operators is refused, and the shipped policy will not sign on fewer than four operators standing behind the round. A deployment pointing all nine servers at one company is refused rather than signed. The receipt carries the operator beside each source, and not a count of them, so a reader does the arithmetic and gets their own answer rather than ours.
What an operator count cannot see is a shared upstream, a shared network path, a shared satellite constellation and a shared implementation, so it is an upper bound on how independent a round was rather than a measurement of it.
Two operators disciplined by the same national laboratory move together when it moves. Two servers reached over the same transit are one on-path attacker. Most stratum-1 servers in the world are disciplined by GPS, and one spoofed constellation moves every operator that trusts it. One defect in a widely deployed server program is one fault across every operator running it. No field in NTP, NTS or Roughtime states any of the four, so nothing in this product can check them, and the number it reports is the largest the round could have been rather than what it was. Where the operator itself is in doubt, two names are treated as one company rather than two, because merging can only lower the count and refuse, while splitting inflates the very floor that is supposed to catch it.
A receipt carries no measurement from any source, so nobody else can recompute its width.
For each source it names an id, a kind, an operator, a timescale, the leap and smear state and whether the source was kept. It carries none of the four timestamps of an exchange, no round trip and no uncertainty a source stated. So a stranger can check that the parts of the width add up to the width and that a majority was kept, and cannot work out any part of it from what the sources said. The width is the agent's arithmetic on measurements only the agent saw. A receipt format that carries them would be a later version than the version 1 that ships, and it is not built.
The operator names the floor counts are strings the signer wrote.
The verifier counts operators from the labels in the receipt rather than taking a count the receipt states, which stops the agent's arithmetic being checked against itself. It does not stop the labels being made up. A signer running nine servers at one company could name nine companies, and nothing in NTP, NTS or Roughtime lets a reader see which company answered. A Roughtime corridor, where one is carried and checked, is signed by a key the reader holds, and every other operator name is the signer's word.
A machine that can reach only the three public Roughtime servers reaches three operators, which is under the shipped floor of four, so it refuses to sign, and nothing that ships lowers the floor.
That path was the seconds-wide one until 2026-09-09. The shipped product now declines it on two counts: the operator floor refuses the round before any width is looked at, and the width ceilings refuse it as well, 250 ms through the resident agent and, on the one-shot command the Action runs, a 2 s ceiling. No option on the command line and no input to the Action lowers the floor. It is min_operators in Policy::default, in crates/clock/src/policy.rs, so lowering it means building from a changed source. That is deliberate, because a product whose independence floor bends to whatever the network gave it today has a floor in name only. Every seconds-wide figure on this list is from before the floor, and none of them is a receipt the shipped product issues.
NTS authenticates a source and can never be evidence for a bound.
The keys come out of a TLS session and are symmetric, so this machine holds the same secret the server used and could compose any NTS reply it can then check successfully. A signature is evidence because the checker cannot produce it. SourceKind::Nts answers no to carries_third_party_signature, an NTS exchange carries no attestation, and nothing from that client appears in a receipt in any of the three evidence roles. It improves the clock and it is not a witness.
Through the resident agent the reading behind a stamp is taken with no network call at all; through the one-shot command the network call that produced it is part of the same few seconds as the stamp.
This is about the reading and the bound and not about the whole run: gathering the three attestations is network work either way, and the receipt says how many it carries. The verifier prints the age of the newest exchange behind the interval on every receipt, so a reader can see how long the model extrapolated for; that age is read off the receipt, and the receipt is the signer's. What the agent buys is not free either: the reading is extrapolated over however long ago the last round was, and the model charges for that, so the term for the oscillator grows as the term for the fit falls.
Every receipt this product issues says its bound rests on the agent's own model, so no receipt yet carries third-party signed evidence for its bound.
A receipt carries whichever of the three evidence roles were gathered for it, which can be fewer than three or none. A role is left out where its party was not asked, gave no usable answer in time, or gave one that was set aside as too far from the reading to bear on it. Each one carried is checked by the verifier against keys a reader chose in advance. What none of them does is support the width: the receipt's own bound is the agent's claim and is labelled as one. The format can express a bound resting on outside signatures and nothing issues a receipt that does.
The resident agent refuses any interval wider than 250 ms, and the one-shot command, which the GitHub Action runs, refuses one wider than 2 s.
Read on 2026-09-15 off max_bound_width in Policy::default, in crates/clock/src/policy.rs, off CI_MAX_BOUND_WIDTH in crates/cli/src/stamp_cmd.rs, and off the max-width input in action.yml. Only the agent holds itself to 250 ms. The one-shot path needs a ceiling of its own, because the widest receipt the one-shot command on a build runner has given us, 287.147 ms wide on 2026-09-14 at sixteen rounds, is already past the agent's. Two seconds is the narrowest interval a Roughtime corridor can state, a radius of one second either side, so a bound wider than that says less than one signed corridor already tells a stranger. It is about seven times that runner's width, and about nine times the bound of 211.3 ms another runner reached on 2026-09-09. The ceiling was 30 s until 2026-09-15, sized for the 16.219 s of width a runner reached against Roughtime alone on 2026-09-08, a round the operator floor has refused since 2026-09-09. Every receipt the one-shot command signed before 2026-09-15 states that older ceiling in its own policy, the one at crates/verify/tests/data/a-real-stamp/ included.
Order is checked two receipts at a time, and nothing walks a chain.
timewitness order reads two receipts offline, checks each of them the way verify does, and keeps two answers apart. The two intervals say which moment came first, and where they touch or overlap the answer is that nobody can say. A receipt that names the other by the hash of its signed bytes says which of the two was signed first, which rests on a hash rather than on a clock and says nothing about UTC. Two receipts signed by different agent keys are not a chain, so only their intervals are compared. An order resting on a receipt the verifier refused does not stand, and where an agent's chain runs against its own intervals the command says that agent contradicted itself and does not pick which claim to drop. A receipt missing from between the two handed over is not seen, and a fork is seen only when the two handed over are its two sides. The claim this product opens with is bounded time and unbroken order, and the second half of it is checked two receipts at a time and no further.
A countersigned exchange shows that two claims are consistent with an order, and nothing more.
The timewitness countersign command that reads one ships from the v0.2 release. Two agents each sign a statement about their own clock, the receiver names the sender's request by its hash, and the command reads the pair offline. Neither interval is third-party evidence for the other, and countersigning does not make it so: the outside evidence lives in the receipts each claim came from, which the exchange names by hash and does not carry. The two intervals are never combined into a narrower one, so where they touch or overlap the answer is that the order is undecided. Where the receive interval sits wholly before the send, one clock is outside its own bound or one party is lying, and the pair cannot say which. A holder of one exchange cannot see whether the same request was answered more than once. A pair signed by two keys nobody has heard of is a valid pair, and nothing in it says who holds either key. A receiver that declines to countersign signs nothing, keeps nothing and stops nothing.
There is no refusal receipt.
A refusal is a return value inside the agent. Nothing signed and nothing portable is produced, so there is no artefact a third party could be shown. The phrase reads as a description of something that exists and it describes something that does not.
Only Linux has a released binary, and nothing in the binary is evidence about time.
From v0.7 a release carries the command line tool as a binary for Linux, on x86_64 and aarch64. Each archive and the digest list beside it are signed with cosign through Sigstore, by this repository's own release workflow, and scripts/the-binaries-are-signed.py --release v0.7 --linux-only checks them. That signature shows the archive came from our release workflow running on main. It is our own identity and says nothing about any receipt the binary goes on to make. No macOS or Windows binary is released, because neither can be signed yet, so on those two systems the command line tool is compiled from source. The Action has no binary either: it builds the command from source on the runner. The verifier is built and works three ways: as a command line tool, as one HTML page that runs from a local disk with no network, and as that same page served at timewitness.dev/verify, which needs no toolchain and no account and reads the receipt in the browser without sending it anywhere. The served page names the commit it was built from, so a reader can build the same page and compare.
Nothing links an agent's key to anybody, except our own.
A receipt proves that whoever signed it held that key. A log of our keys is served at timewitness.dev/key-log.txt and names the keys of our two Roughtime servers and the one agent key we vouch for, which signs our own receipts from 2026-09-24, with the window it is ours in. That is our own word about our own key and it is not third-party evidence. The receipt committed in this repository was signed on 2026-09-09 by an earlier key whose private half cannot be accounted for, so the log does not name it. Any key the log does not name, that one included, is refused against it as not ours, and a reader who does not already recognise that key learns only that one key signed this. The window is judged on the receipt's own reading, so it catches a receipt that says it was signed outside the window and not one that lies about when. The v0 release refuses that log's format by name, and v0.4 reads it. The Action generates a key on the runner where none is supplied, which is what keeps the install to one line and is exactly as meaningful as that sounds.
There is no first-run figure from anybody who is not us.
Nobody outside has run it.
One freshness beacon works, not two.
The design names drand, the NIST beacon and the UChile beacon, and asks for at least two. Only drand is verified here. On 2026-09-07 the NIST beacon's pulses did not satisfy its own published rule that the output value is the SHA-512 of the signature, and the certificate a pulse names by hash carried a 2048-bit key while the signature was 4096-bit; those two cannot both be right, so no client was written for it. The UChile beacon was not serving its interface at all.
One kind of final witness works, not two.
RFC 3161 timestamp tokens are verified. An OpenTimestamps anchor into a public chain is named in the design and is not built.
A sleep is detected and has never been watched happening.
An operating system keeps two counters, one that stops while the machine is suspended and one that carries on, and the difference between them is how long the machine was away. The agent reads both; on a resume it throws away everything it measured before the gap and refuses to stamp until it has synchronised again. The arithmetic is tested against counters a test wrote down. Nothing has put a real machine to sleep and watched it come back, because that is not something a test suite does to the machine it is running on. A sleep shorter than a quarter of a second is below the resolution of the counters being compared and is not seen at all.
On Windows, continuous integration installs and removes the agent as a service and nothing more.
It does that on Windows and macOS runners for every pull request that touches the agent, and runs everything else on Linux. The Windows time service contends with any other discipliner, which is why the agent measures and vouches rather than taking the clock, and the counters that see a suspend are read through Windows interfaces that no build runner in this project executes. They were run on a Windows desktop on 2026-09-08, and they run there whenever the suite does, which is a developer's machine rather than a gate.
An agent that starts inside a leap smear may not know it.
A leap second is announced hours before it happens and the announcement is cleared the moment it has, which is the moment a source spreading the second across a day starts diverging from one that inserted it. An agent that was running sees the announcement, remembers it, and stays on guard for the whole of the window. An agent started after the announcement cleared never saw it. It can still refuse where the sources disagree about smearing by an amount that could be one second spread out, and where every source in the pool smears the same way there is nothing to disagree about: the bound then holds those sources and those sources are deliberately not on UTC for that day.
Nothing here stops any of it.
A machine suspends whether the agent likes it or not, a second time service sets the clock before the agent sees anything, and a leap second arrives on schedule. Every one of these is recorded after the fact. What the agent controls is whether it puts its name to a reading taken afterwards.
Roughtime is an Internet-Draft and not an RFC.
The version implemented is draft-ietf-ntp-roughtime-19, with intended status Experimental. Read off the IETF datatracker on 17 September 2026, that revision was in the RFC Editor Queue, which is where a draft waits before it is published as an RFC, and a place in that queue is not a publication date. The version number on the wire is the draft's own testing number rather than the one the published RFC would use, so publication may change what a server answers on. What moves then is the reference rather than the receipts: a receipt already issued stays checkable, because the verifier carries the rules it was signed under.
Only RSA signatures are checked on timestamp tokens.
One of the four free authorities tried signs with ECDSA, and its tokens are refused by name rather than skipped past.
A pinned certificate goes stale.
When a timestamp authority replaces its signing certificate, fetching a new token starts failing until the pin is updated. Tokens already inside receipts stay checkable, because the certificate travels inside the token.
How to check this rather than believe it
Every claim here about what the code does has a test behind it, and the tests are in the repository. Three reports name each check, name the test that catches it, and record the run where that check was taken out of the code and the test watched failing. The evidence itself, captured from the real servers, is committed beside them.