aboutsummaryrefslogtreecommitdiffstats
path: root/src
Commit message (Collapse)AuthorAgeFilesLines
* access: a token's limit narrows what an endpoint takestv48 hours1-19/+62
| | | | | | | | | | | | | | Review 4, F1: `authorize` decided whether a request's endpoint was allowed by the user's full capabilities, and applied a token's limit only afterwards, to the `Access` it left for handlers. The store and the users endpoints read that; nothing else does. So a token saying less than its user may do (the dashboard's hand-over token, reading the member list only, for five minutes) did whatever the user may among the other endpoints, on every node: for an operator, forgetting members, proposing or aborting remakes, exporting the document, the quarantine, the log filter. The limit is now applied first, and the endpoint decided by what is left (`allowed`, its own function now, with a test: the hand-over token of an operator may look, and nothing else).
* api: a stream opened with a token ends with ittv2 days5-11/+119
| | | | | | | | | | | | | | | | | | | | A token was checked when a request came, and a stream answered to it (the dashboard's events, a subscription's links and messages, the store's streams) then went on for as long as the client kept it open: past the token's expiry, past its user's tokens being revoked, its user disabled or removed, its session ended. Now a middleware inside `login::bearer` (`login::streams_end`) ends a streaming answer (`text/event-stream`, `application/x-ndjson`) to a request with a token at the token's deadline, its expiry, or as soon as the document no longer takes it (`token::still_taken`, the checks `verify` makes of the document, now its own function): looked at when a change touches protocol data (the root, a switch, among it), and when a session ends on the node itself in a swarm without a registry (`AppState::session_ended`). No polling: a timer for the expiry, and the changes as they come. The client reconnects with the token it has then; a refreshed token is a new one, so a stream lasts a token's lifetime at most (12 hours by default). `futures` is a dependency of every build now (for `take_until`), not only of libp2p's and Tor's.
* access: ending a single sessiontv2 days8-8/+176
| | | | | | | | | | | | | | | | | | | | Logging out of the dashboard (`DELETE /auth/session`) only cleared the cookie: the session's tokens stayed taken until they expired, by every node. Ending them took `POST /auth/logout`, which ends all of a user's sessions. Now `DELETE /auth/session` ends the session of the token it comes with (the cookie, or a bearer token): where the swarm has a registry, it is recorded as `_continuum/sessions/<user>/<session>` (`registry::session_path`), its value until when the session would have lasted, and every node refuses the session's tokens once the change reaches it (`token::Refused::Ended`): the page's own, its narrow tokens, and one handed to another member's dashboard, all refreshed from it and keeping its ID. The rules take the entry from the user itself or one who manages users, as a logout. A remake's snapshot leaves out entries past that time, a user's map where none is left: their tokens are expired anyway. Without a registry the document can't record it: the node ending it refuses the session's tokens itself (`AppState::ended_sessions`, until it restarts), others take them until they expire, as a logout there is refused.
* dashboard: nothing shown or read after logging out, back button includedtv2 days1-2/+18
| | | | | | | | | | | | After "log out" the page went to the login page, and the browser's back button brought it back as it was (kept whole in its history): the map, the peers and the console went on updating, by the stream it still had open and the subscriptions' token it still held, though the session had ended (the document box, reading anew, got 401s). Now leaving for the login page (`toLogin`: logging out, or the login ending) stops the page's reading first: its stream, its subscriptions, its timers and tokens. And a page the browser brings back whole (`pageshow`, `persisted`) loads again instead, so it reads with the session it has now, and without one goes to the login page.
* dashboard: going to another node's dashboard without its login page in betweentv2 days4-20/+36
| | | | | | | | | | | | | | | Going to another member's dashboard showed its login page for a moment: a browser without a session there was sent from `/admin/dashboard` to `/login` (by `auth::basic_auth`), which began a session with the token handed over and went on to the dashboard. Now the dashboard's page is served without authentication, as the login page is (it holds nothing: what it shows it reads with the browser's session). Opened with a token handed over, the page begins the session with it (`POST /auth/session`) before it reads anything; without a session, `obtainTokens` goes to the login page, as the redirect did. A request without credentials is refused at once rather than after the 1.5 s that slow down guessing passwords: nothing is guessed there, and a page without a session goes to the login page without waiting twice for it.
* dashboard: tokens renewed after sleep too, never basic authenticationtv2 days2-18/+55
| | | | | | | | | | | | | | | | | | | | The page renewed its token half way through its life by a timer. A timer fires late where the computer slept or the tab was in the background, by when the token (and the session cookie, which lives as long) may have ended: the page then fell back to `POST /auth/login`, which takes a password the browser keeps (basic authentication), and without one retried every 30 s, logged out without saying so. Now whether the token is half way through its life is looked at again when the page shows and every 30 s (`refreshIfDue`, one renewal at a time), so it is renewed while it still can be, until the login ends (`--session-lifetime`). Where the login ended, or there is none (400, 401, 403), the page goes to the login page; a node away is tried again in 30 s. And the page no longer gets in by a password the browser keeps: only by its session or through a proxy naming the user (`throughProxy`, `login: "header"`). The node never challenges a browser for a password either (`WWW-Authenticate`, the browser's prompt): not only on the dashboard's paths and for a page's own requests, but for any request with `Sec-Fetch-*` or a `Mozilla/…` user agent. curl sends the password unasked with `-u`, as before.
* dashboard: a subscribed node's own figures when it is in focustv2 days2-6/+77
| | | | | | | | | | | | | | | A node in focus showed this node's `/peers` entry for it and the links it reports: what this node knows of it, never what it says of itself, though the page subscribes to it. Now a subscribed node in focus shows its own figures first (`Node:`), as this node's are (its `/admin/stats`), then this node's entry for it (`as this node sees it`) and its links, each a branch. A link in focus shows each end's paths to the other as that end has them: this node's, and a subscribed node's from its `/peers`. The page reads them from the node every 2 s while it is in focus (`readFigures`, `figuresOf`), with its narrow token, which may look at a node as any user with a capability may; pages of other origins may now call `/admin/stats` and `/peers` (`cors::open`), as they do a node's links and messages. Where they can't be read, the box says why.
* dashboard: a subscribed node's message written out, read from it when showntv2 days4-15/+70
| | | | | | | | | | A subscribed node's messages come without being written out (their stream leaves the text out: up to 8 KiB each, 500 at a time), so the figures box said only when, both ends, type and size of one. Now the node answers for one message by its number, `GET /admin/dashboard/messages/own/{seq}` (`{"text": …}`, `404` once it isn't kept), which pages of other origins may call as they do its streams (`cors::open`), and the page reads it when the message is shown, with its narrow token, and keeps it on the message. Where it can't be read, the box says why.
* dashboard: the transport each message went by, in the consoletv2 days4-35/+87
| | | | | | | | | | | | | | | The console said with whom a message went, not how: the message log was noted above the identity layer (`CountingTransport`), where a message is to or from a node, and the address it went to (the paths layer picks one, `Paths::send_first`) or came from is gone. Only the paths layer's own PINGs and PONGs, logged under their addresses, had one. Now the identity layer notes the node's messages (`MessageLog::set_addressed`), with the address each went to (`IdentityTransport::send_proven` returns it) or came from; `CountingTransport` leaves them to it, and still notes them where there is no identity layer (tests). A console line ends with the transport (the address's scheme, in its colour on the map, the address in the tooltip), and the figures box shows the address. A subscribed node's messages carry theirs too, and the map runs every message along its transport's line, not only the PINGs and PONGs.
* access: what a node holds alone by `_continuum/local/<node ID>/<name>`tv2 days4-27/+76
| | | | | | | | | | | | | | | | Setting a node's log filter took `write` on `_continuum`, all of the protocol data: whoever could change one node's logging could change the swarm's members, remakes and users too. Now what a node holds alone has paths of its own for capabilities, under `_continuum/local/<node ID>/`: `log` (`/admin/log`), `config` (`/admin/config`) and `debug` (`/debug`, always `write`), `read` to look and `write` to act (`api::access::needed`, which takes the node's ID). So `_continuum/local/<node ID>` is everything on one node, `_continuum/local/*/log` the log filter on every node, and holders of `_continuum` keep it all. Nothing is stored there: the store refuses writes under `_continuum/local`, and the rules refuse a change touching it, as for epoch metadata. The quarantine stays with `_continuum`: its entries are everyone's writes, which a node-local capability mustn't show.
* access: `*` for any one key in a capability's pathtv2 days4-33/+68
| | | | | | | | | | | | | | | | | | A capability's path covered by prefix only, so what is the same at every key of a map (each node's log filter, say, at `_continuum/local/<id>/log` from the next patch on) took a capability per key. Now a `*` segment stands for any one key, as it already does in signed paths and sealed places (`envelope`, `sealed`, which now share one `agree`): * covering (`Capability::covers`): the pattern no longer than the path, and agreeing with it segment by segment; * listings (`reaches`): pattern and path agreeing on the segments both have, so every key on the way shows, nothing past where the pattern differs; * narrowing (`Capability::holds`, for a token's limit narrowed again and a refresh's): at or below, a `*` only under a `*`: a limit naming one key never widens to all. A `*` first is outside protocol data, so it never covers `_continuum`, as `[]` doesn't. A key that is `*` itself can't be named alone. Every node judges changes by capabilities, so all must read them alike: they run the same version.
* paths: PINGs of a path take turns with the peer'stv2 days1-4/+82
| | | | | | | | | | | | | | | | | | The node's keepalives take turns between the two sides (`PeerState::keepalive_due_at`), but the paths layer pinged every path in use every `probe_every` (the keepalive interval, 30 s at least) whatever the peer did: a PING from the peer over the path didn't count. So both sides pinged each path every interval, often within a second of each other (seen on the dashboard: a PING answered, and our own PING right after it). Now a PING from the peer over a path is noted (`PathState::peer_probe`), and a path is pinged again (`rested`) an interval after the peer's PING, if that came last; after ours, an interval and a half while the peer pings too (its PING within two intervals before ours), so its turn comes first; else an interval. One exchange per interval goes over a path, as the measurements' "two keepalives per neighbour per interval" says, and each side measures every other interval; a shorter `--keepalive` measures more often. A path whose way out broke while the peer's PINGs still come in is found by our own PINGs half as often; a failed send or unanswered traffic (`suspect`) still pings at once.
* api: a log line per HTTP requesttv2 days3-5/+67
| | | | | | | | | | | | | Requests weren't logged at all, short of what a handler logs itself. Now a middleware, outermost on the API (and on a relay-only node's), logs a line per request as web servers do: method and path (with the query), status, the milliseconds until the answer began (a stream goes on after), where it came from (the address, or `unix`), and the user (`-` where none was authenticated). The user comes back on the response: `basic_auth` puts it there, for a token as for a password or a trusted proxy's header. At debug, under the target `http` (`RUST_LOG=info,http=debug`, or `PUT /admin/log`): an open dashboard alone would fill the log at info. With the target off, the request passes untouched.
* remake: earlier epochs' metadata read as the snapshot built ittv2 days2-9/+55
| | | | | | | | | | | | | | A snapshot leaving out earlier epochs' metadata (`snapshot::left_out`) and a rejoining node (`catchup::prepare`) read it from the document as it stands. It is never writable through the store, and under a policy the rules take a change to it only from an epoch's first; but with no policy, screening judges only an epoch's root, so a member's own change could rewrite or remove an entry: a replay window narrowed or widened, a translation or a clock changed, a strict remake's entry gone. Now both read it at the epoch's snapshot change (`snapshot::as_built`), as `verify_document` reads the epoch's own: a snapshot from its base's epoch's, a rejoining node from the target's. Every earlier entry is a copy in that change, so what was written there since doesn't count. The first epoch follows none and has none.
* dashboard: the replay window and archive retention in the remake dialogtv2 days1-0/+32
| | | | | | | | | The remake dialog set the kind, its input, the settle time and the switch delay, but not the replay window or the archive retention. Now each has two fields, epochs and an age, their defaults as placeholders: both empty sends nothing (the node's defaults), one given sends that limit alone, as the API reads it; in the body, or for an import in the query (`replayWindow`, `archiveRetention`). Epochs that aren't a whole number are refused in the page.
* remake: replayWindow and archiveRetention in an octet-stream import's querytv2 days1-2/+50
| | | | | | | An import uploaded as `application/octet-stream` takes `rules`, `baseHeads`, `settle` and `switchDelay` in the query, but not the retentions: for those the document had to go base64-encoded in the JSON form. Now `replayWindow` and `archiveRetention` go in the query too, as JSON like the rules (`{"epochs":1,"age":"7d"}`, as in the body).
* remake: leave out earlier epochs' metadata past the replay windowtv2 days2-5/+123
| | | | | | | | | | | | | | | | | | | | | Every snapshot carried the metadata of all earlier epochs (`_continuum/epochs`) forward, so it grew by an entry, a clock per actor among it, with every remake. Only a rejoining node reads an earlier entry: a node on epoch P reads P+1's (its clock, when it was proposed, its replay window) and every one up to the swarm's epoch (kind, translation, whether it was strict). It replays only while P+1's replay window covers it; beyond, its writes are quarantined whatever the entries say. So a snapshot now leaves out the generations older than the oldest whose replay window still covers a node on the epoch before it (`snapshot::left_out`), judged by the remake's `proposedAt`: the same on every node, and earlier than any rejoining node's clock, so nothing a node can still replay through goes. Metadata that doesn't read stays, with all after it. A strict remake's metadata stays for good: by it a rejoining node, however far behind, tells a strict remake of a swarm strict already (`catchup::prepare`, which reads the generations it finds). There is at most one, short of such a remake. No new key and no change to the rejoin: a node beyond the window that finds some entries and not others quarantines with "cannot translate" rather than "outside the replay window".
* access: keep an SSH key's commenttv2 days3-4/+23
| | | | | | | | A key's line was stored as the key alone (`ssh-ed25519 <base64>`), whatever comment it came with, both from `PUT /auth/keys/<name>` and from `--bootstrap-keys`. Now its comment is kept after it (`PublicKey::kept_line`), the spaces between its words made one. The comment checks nothing: every use of a key parses the line, and the parse reads the key alone.
* dashboard: no subscription to this node itselftv3 days1-1/+8
| | | | | | | | | | | | A page opened from another node's page takes that page's subscriptions along; one of them can be this node, another there. The page then followed this node as a subscribed one too, and showed its messages twice, from its own log and from the subscription (`→ LLL PING` and `LLL ← RRR PING`, the same message), on the map as in the console. The page follows no subscription of its own node: not when it knows which node it is, and one begun before that stops once it does; its messages, if any came, are left out.
* dashboard: keys edited, the last removed with a password, failures kepttv3 days5-85/+211
| | | | | | | | | | | | | | | | | | | | | The credentials dialog listed a user's keys whole, to add one or remove one but the last, and any failure replaced the dialog with the error, the form lost. * A key is shown short: its type, the first and last four characters of what tells it apart (an Ed25519 key's base64 after the part all of them share), its comment; the whole line in its tooltip. * A key can be edited, its name and its line: saved under the name, a new name written first and the old one removed. * A user with a password on this node (`--htpasswd`) may remove its last key: it logs in by the password. GET /auth/keys says so (`password`), and DELETE /auth/keys/{id} takes it then; the rules no longer keep the last key on every node (no other node can tell who has a password where; under a strict policy the signed entry keeps one still). * What fails says why beside where it was asked (the add form, the edited row, the removed one), which stays as it was, to be set right and tried again.
* dashboard: a node's menu on a right-click, and going to its dashboardtv3 days1-5/+14
| | | | | | | | | | A right press on a node started dragging it, as any press did: the map took hold of the pointer, so the right-click went to the map and the node's menu didn't open, and letting go put the node in focus. The right button now opens the node's menu alone. Its entry for the node's dashboard is `go to its dashboard`, greyed where the node publishes none (and on this node's own), its tooltip saying where it goes or why not.
* dashboard: move pull slider to link legendtv3 days1-29/+43
|
* dashboard: an entry's menu left of the fingertv3 days1-4/+12
| | | | | | | Opened by a finger held on an entry, its menu had its top left corner at the finger, under the hand holding it, a right hand mostly. By touch it now has its top right corner there; opened by a pointer, its top left, as before.
* dashboard: the document box edited from its menus alonetv3 days1-45/+19
| | | | | | | | With the entries' menus (1238), editing had three ways in: the double-click, the `+` on a map's or list's row, and the menu. The double-click and the `+` go; the menu does all of it. The title, which stands for the root (its row isn't shown), now takes the right-click too: its menu adds a key at the top level.
* dashboard: an entry's menu in the document boxtv3 days1-25/+192
| | | | | | | | | | | | | | The document box edited an entry by a double-click and added to a map or list by the `+` on its row; a key couldn't be renamed, and a list's `+` always asked for an index. A right-click on an entry, or a finger held on it half a second, opens its menu: `Edit value` (as the double-click), `Rename key` for a map's key, and on a map `Add a key`, on a list `Append an item` or `Insert an item at…`. Renaming reads the value, writes it under the new key and deletes the old one (two writes, the value as JSON; a key that exists already isn't overwritten). Inside a field being edited, and where an entry offers nothing, the browser's own menu shows.
* dashboard: where a stream's lag comes fromtv3 days2-25/+63
| | | | | | | | | | | | | A stream's lag in the tooltip of `behind: …` was one figure, from when a node logged a message to when the page had it: a second and a half for a node a LAN away said nothing of where it went. The node now stamps each `messages` event with when it sent it (`{"sent": …, "messages": […]}`), and the tooltip splits each stream's lag: how long its batches waited in the node, from logging the first message to sending the batch (the node's own clock: exact), and how long they took on the way, from sending to arriving. This node's own stream is always listed, beside the three slowest.
* dashboard: the console in the order messages wenttv3 days1-4/+20
| | | | | | | | | | | | The console appended messages as their streams brought them, a subscribed node's later than this node's by its path, each line with its node's own clock's time: lines of one moment came apart, and their times read out of order. Each message is now listed by when its node logged it, on this node's clock, as the map runs them (`aheadOf`): one a slower stream brought late goes in among those of its moment. The time shown is on this node's clock; a subscribed node's own clock's time is in its tooltip.
* dashboard: messages on one timeline, when they wenttv3 days1-28/+120
| | | | | | | | | | | | | | | | | Each node's message stream comes its own way to the page, a subscribed node's over the network to it: messages logged 16 ms apart on three nodes ran over a second apart, as their streams arrived. Messages now run when they went, on this node's clock: each at the time its node logged it, taken back by how far that node's clock is ahead of this node's (the map's clock chain, `aheadOf`), a while behind real time (`replayDelay`): the most any stream lagged in the last 30 s and a tenth of a second more, shown after the sliders (`behind: …`), each stream's lag behind the fastest in its tooltip. The browser's clock is against all streams alike and drops out. A message known only from its receiver (its sender not subscribed) is placed at its arrival, starting a crossing before. Messages lagging more than 10 s (a gap) don't run, nor hold the map behind.
* dashboard: messages as they cometv3 days3-105/+143
| | | | | | | | | | | | | | | | | | The node sent its messages to the page every 2 s (`messages` events, this node's and a subscribed node's), and this node's ran on the map from its counts, which come with the state: the map showed a message 2 to 4 s after it went, the messages of each 2 s spread evenly over the next. The message log now tells those waiting of each message logged (MessageLog::logged_after), and both streams send what was logged a tenth of a second after the first of it (MESSAGES_GATHERED): a busy node about ten events a second, a quiet one each message soon after it went. The page runs messages as their streams bring them, this node's from its own log too (with the addresses its PINGs went to), as far apart as they were sent; more than six on a link and way at once are shared out by kind over the moment, and after a gap only the last 2 s run. The counts still give the rates and what was delivered.
* dashboard: messages run as they left; a link breathes red for those losttv3 days1-11/+126
| | | | | | | | | | | | | | | | | | A message between this node and a subscribed one ran from this node's counts both ways: one the subscribed node sent ran once this node had counted it received, as it arrived. Each message now runs from its sender where the page reads the sender (this node's counts, a subscribed node's messages), so it runs the way it went, also where it never arrived; only one from a node the page doesn't read runs as it arrived. The console lists them as before. Where the page reads both ends of a link, what one sent the other should count received: a message sent and not received within 5 s makes the link breathe red once (`breatheLink`), its tooltip saying how many. Kinds the paths and identity layers count under addresses are left out, as this node's counts leave them out; a subscribed node's tick that came short (the latest 500 of more) starts the count afresh, so what the page didn't see isn't taken as lost.
* dashboard: messages follow the line they went overtv3 days1-27/+68
| | | | | | | | | | | | Dots and flashes ran straight from sender to receiver, whatever line the message took: a PING over another transport, drawn as a curved line beside the one in use, crossed the map off its line. Each message now runs along the line it went over, as drawn (`route`: the link's quadratic curve, its control point where renderGraph put it): a message naming the address it went to (the paths layer's PINGs and PONGs) over that address's transport's line, any other over the line in use; a flash's streak bends with it.
* dashboard: how hard links pull, on a dynamic maptv3 days1-2/+26
| | | | | | | | On a dynamic map, links pull toward their lengths against the nodes pushing each other apart, at one strength. A `pull` slider before the layout's toggle, shown while dynamic, sets it from a quarter to four times as hard (2 to its value, once at first), kept in the browser; the map moves to the new balance smoothly.
* dashboard: no dotted links for an end the page doesn't readtv3 days1-11/+7
| | | | | | | | | | A link was dotted where an end reported no links. Members used to report their links to every dashboard (LINKS); the page now reads a member's links only while subscribed to it, so a dotted link said no more than that its other end isn't subscribed, which the node shows already (hollow), and most links were dotted. The style and its legend entry go; the link's tooltip says when one end alone reports it.
* dashboard: every kind of message coloured and in the legendtv3 days1-4/+8
| | | | | | | | The map's dots and the console coloured six kinds of message; PROBE, PRUNE/GRAFT, NEIGHBOUR and REFUSALS were drawn in the text colour and missing from the legend. Each kind (`traffic`'s counters) now has a colour, and the legend names them all, saying which messages a kind stands for where its name doesn't.
* changes: Context where it is usedtv3 days1-1/+2
| | | | | | Since load_delta is for tests (1190), nothing else in changes.rs uses anyhow's Context, and builds without tests warned of an unused import. load_delta imports it itself.
* screen: what apply_bytes did, without how many it appliedtv3 days1-3/+0
| | | | | | | How many changes apply_bytes applied was read only by the handler of SIGNATURES, which re-screened the changes waiting for them and published those that went in. With SIGNATURES gone (1219), nothing reads it: Outcome loses the field.
* sync: a message carries the signatures of its changestv3 days4-63/+57
| | | | | | | | | | | | | | | A sync message carried the signatures its peer wasn't sent yet, by a cursor per peer. Changes sent again, as after a message taken as delivered and lost on the way, went without them: the peer was past them. Under a policy requiring signatures they waited until the writer's next signed change, or the next session. A message now carries the signatures of the changes it carries (Signatures::for_changes), whatever the peer was sent before: changes sent again arrive vouched for as the first time. Past 256 (a message carrying the batches of many writers), each writer's latest instead, as a delta carries. The cursor goes, and with it resetting it after a failed send or a new session.
* sync: a test exchanging until level, not one answertv3 days1-0/+17
| | | | | | | | | delivered_messages_are_answered_with_changes_we_lack failed now and then: after the answer, the node still lacked the peer's change. Automerge's sync tells what each side has by a Bloom filter, whose false positives take more rounds. The test now exchanges up to four rounds more, each asking for what is still missing, until both are level.
* sync: signatures sent again after a failed sendtv3 days1-6/+16
| | | | | | | | | | A sync message whose send fails gives back the changes it carried: the next message takes them again. The signatures it carried weren't given back: the peer's cursor stayed past them, so the changes went again without them, and under a policy requiring signatures waited for them until the next session; SIGNATURES no longer covers that. A failed send now sends the peer all signatures again with the next changes (each actor's latest, a few hundred at most).
* swarm: no SIGNATUREStv3 days10-167/+11
| | | | | | | | | | | | | | Signatures sent on their own (SIGNATURES, every second, to each peer not sent them yet) flooded every link: a signature made after its change left, or one that came with changes, went to every neighbour, whether or not it got the change along with its signature elsewhere. With every change of a node signed as it leaves (1217) and signatures going where their changes go (1218), none is left for them to carry: the message, its task and its traffic counter go (wire version 46). Signatures waiting for their changes are offered as before when sync messages bring them, and the changes waiting for signatures judged again with the next batch.
* sync: signatures go with changes onlytv3 days2-2/+19
| | | | | | | | | | | A sync message took the signatures its peer wasn't sent yet whenever it had a body, an Automerge message saying only what we have included. A signature belongs with the changes it vouches for: they go along the trees, and a peer that got them elsewhere got the signatures with them. Signatures now go in a message that carries changes (pushed, offered as a delta, or in an Automerge message carrying some: SyncSession:: carrying), and in no other.
* signatures: sign what is senttv3 days6-75/+55
| | | | | | | | | | | | | | | | | | | A node signed its changes no rule signs a while after them (--sign-every 1s): the change left at once, unsigned, and its signature followed on its own a second later, to every neighbour, over every link. A few writes made a cascade of SIGNATURES. Under a policy requiring signatures it signed every change as it was made instead. A node now signs its latest change, if no signature vouches for it, as a sync message takes it (--sign-every sent, the default): the signature leaves in the same message, and goes where the change goes. One signature per message, however many changes it carries, each vouching for those before it: a slow writer's every change is signed, a fast writer's batches are, at no threshold to pick. A policy requiring signatures signs what is sent too, even with `none`. `all` still signs every change as it is made; changes to protocol data, listed paths and ?signed=true writes are signed as they are made, as before. The interval, and the task signing on it, go.
* signatures: a node signs at start what it kept unsigned when it stoppedtv3 days2-0/+60
| | | | | | | | | | | | | | A node commits a change, keeps it in its document file, and may stop before any signature vouches for it. It starts again under a new actor and only ever signed its current actor's latest change: the old one stays unsigned for good. Peers pulling it then wait for a signature under a policy requiring them, and so does everything the node writes after, building on it. Signing every change at commit hid this under such a policy; signing what is sent, next, won't. At start, before anything leaves, a node signs each of its own changes at the document's heads that no signature vouches for: nothing it made since builds on them, so they are heads.
* roots: a peer contradicting --genesis-root refused at the handshaketv3 days1-2/+18
| | | | | | | | | Review 3, R5: a node given its root (--genesis-root) refused a peer naming another only change by change while its document was empty, each with an entry among the refused; the handshake went through, as there was no root of its own yet to compare with. The root given is now compared too: a peer naming another is refused at the handshake, logged once, as one whose document starts from another root.
* state: refused writes limited for the node tootv3 days2-17/+53
| | | | | | | | | | | | Review 3, R5: --refused-writes-per-minute bounds each user, but the cost it guards, a document copy under the write lock, is the node's: ten users, or ten accounts of one, are ten times the budget. The node now takes six times a user's limit within a minute in all (refused::NODE_FACTOR); past it, every user with a write refused within the minute is answered 429 untried, until the oldest leave it. A user none of whose writes was refused writes on, so honest writers aren't held up by others' refusals.
* screen: judged after the policy is gone, tootv3 days3-6/+64
| | | | | | | | | | | | | | Review 3, R4: whether a batch was judged followed the receiving document's policy now (and whether the batch writes one). After the policy was removed, or replaced by a version the node doesn't know, changes written under it and received late went in unjudged, while nodes that had them earlier judged them; REFUSALS gave up likewise. Screening now remembers that the document held a policy, known or not (Screening::policed), and judges what it receives while it does, each change by the policy at its dependencies; REFUSALS judges then too (not under a policy the node doesn't know, as before). Not across a restart, which access.md says.
* remake: a strict remake of a strict swarm isn't followedtv3 days2-0/+28
| | | | | | | | | | | | | | | Review 3, R3: 1186 kept honest nodes from proposing or voting for a strict remake of a swarm strict already, but a majority of members, each with keys of its own, could still commit one, and switch and catchup followed it without asking what kind of remake made the epoch: the registry, policy and bans of a strict swarm changing by a member majority, the honest minority following. A node now refuses to switch to such an epoch (its document's policy strict, the remake's kind strict), and catchup refuses to rejoin one whose chain of epochs holds a strict remake after the swarm was strict (as the old document is, or once a remake since made it so). access.md says so.
* roots: a joiner takes a root once its seeds named ittv3 days5-32/+128
| | | | | | | | | | | | | | | | Review 3, R2: the root pin was set by the first HELLO_REPLY, and the first sync, a round trip later, took that root; a peer naming another afterwards was refused as another history. So the seed that answered first decided, not "none while they disagree", and a malicious seed makes itself fastest. A node joining with an empty document of the first epoch now takes no root (RootPin::Awaiting, roots left for later) until each of its seeds named one, or ROOT_WAIT (10 s) passed since the first did; then the one they name, none while they differ, as before. With --genesis-root it takes that one alone, at once. access.md, the option's help and example.toml say so; switching roots later, while the joiner holds nothing of its own, is postponed.
* signatures: tell held changes as the screen doestv3 days1-4/+4
| | | | | | | | | | With every message's signatures offered now (1205), Signatures::offer runs for each, and it asked get_change_meta_by_hash whether the document holds each signature's change, and each one waiting, up to 4096 of them: Automerge rebuilds the change's metadata from the document's operations to answer. Ask changes::holds instead (1197), there and in forks and kept_for; the metadata is still read where it is used.
* screen: signatures required as the policy a change came under saystv3 days5-51/+96
| | | | | | | | | | | | | | | | | | | | | Review 3, R1: what a node received was vouched for only while its own document's policy required signatures. A joiner's first sync, and every node's message bringing `required`, went in by the rules alone: a malicious first peer could plant changes under other members' actors at their next seqs, their real ones then failing as duplicates for good. The signatures a message carries are always offered now, and the screen always has them: a change waits for one where the policy at the heads it is judged at requires it (rules::Reads::required, read once per heads), whatever the receiving document holds. Changes written under a policy not requiring signatures wait for none, as on every other node; the check for forged changes runs always. Changes waiting for a signature are judged again whenever signatures come. The test of signatures required now takes the history into an empty document; a new one takes changes written under a policy not requiring them without any.