aboutsummaryrefslogtreecommitdiffstats
Commit message (Collapse)AuthorAgeFilesLines
* stats: the writers of the documenttv34 hours5-3/+34
| | | | | | | | | | | How many writers (Automerge actors) the document's changes have matters: a writer is a node's run (each start, rejoin or drop makes one, a start keeping the one of before with `--keep-actor` after a clean stop), and every transfer of the whole history carries a signature for each. `/admin/stats` now says it (`document.writers`, and `document_writers` for Prometheus), counted from the changes' metadata alongside the saved size's measurement in the background (at least a minute apart), as that takes time proportional to the document too; an export, which measures the size alone, keeps the count of before. The dashboard's figures show it with the rest.
* document file: a clean stop marked, and `--keep-actor` writes on as the same ↵tv34 hours6-22/+162
| | | | | | | | | | | | | | | | | | | | | | actor A node writes as a fresh Automerge actor at each start, so no change number of an actor comes twice: one sent before a crash but not yet appended to the document file would otherwise be made again, differently. An epoch thus gathers a writer per start, and every transfer of its history carries a signature per writer. The document file now says when it holds everything the node wrote: * at a clean stop, after the last append, an empty `Closed` record, only where the file is up to the document's heads (ready, not rejoining); * as a node starts on the file, before anything is written, an empty `Opened` record, so a crash from then on leaves a file without the mark; * the header names the actor the node writes with (`current`). With `--keep-actor` (off by default; needs `--document-file`), a node starting on a file that ends with `Closed` writes on as that actor. A crash, a drop, a switch or a rejoin still starts a fresh one, so an epoch's writers come to one per node plus one per such event. lifecycle.md says so.
* auth: whoami says what the request may dotv37 hours4-3/+89
| | | | | | | | | `GET /auth/whoami` lists the user's capabilities (`caps`) and its token's `limit`, if any, side by side; what the request may do is what both give, as endpoints decide it. It says so now (`may`): the capabilities, or with a limit each capability met with each of the limit's (`Capability::meet`): at the deeper of the two paths, a `*` of one taking the other's key there, with the rights both name; nothing where the paths part, no right is shared, or the path is protocol data one of them doesn't name.
* docs: what is postponed or rejected stays in its own two filestv37 hours9-19/+12
| | | | | | | | | Docs and comments outside future-work.md and review-rejections.md say what the code does, and point nowhere else for what it doesn't: the architecture page's map, the transports' forwarders, keys with `/`, a strict swarm's registry, trust.md's "Not yet" (now saying what isn't protected, in a sentence), the topology bench, and the comments of the change benchmarks, capabilities' grantor, tokens, dissemination, refusals and topology.
* signatures: a transfer carries one signature per writer in ittv37 hours5-53/+118
| | | | | | | | | | | | | | | | | | | | | | Under a policy requiring signatures, a node takes a change only once a signature vouches for it: one of the change itself, or of a later change of the same writer. A writer is a node's run, each start, rejoin or drop making a fresh actor, so an epoch gathers writers for as long as it lasts, and a transfer of its whole history (a joiner pulling the document, a delta) must carry a signature for each of them, or the joiner waits for the rest for good. A message's signatures are now chosen by what it carries: * changes, up to 256 signed among them: each one's signature; * more (a whole document pulled): each writer's latest signed change among them, found by the document's change metadata (`Signatures::for_changes`), vouching for the rest; * a delta: each writer with changes in it (`get_changes_meta(from)`), its latest verified signature (`for_delta`). A message carries up to 65536 (6 MiB), and as many wait at the receiver for their changes (a delta's come after it). So a transfer leaves writers unvouched only in an epoch with more than 65536 runs writing since it began; a remake starts the count again. Wire version 48.
* docs: a strict swarm's registry against a minority of members, not a majoritytv38 hours1-1/+7
| | | | | | | | | | Review 4, R3 partly: nodes refuse a strict remake of a strict swarm by its kind, a label the proposal and the snapshot's metadata carry. A majority of members labelling one `compact`, its snapshot holding the registry it wants, gets it followed: nodes whose own build differs take the quorum's snapshot. access.md says outright that a strict swarm's registry, policy and bans change by its users' signatures against a minority of members; comparing a downloaded snapshot with the node's own build at the signed places, and refusing the switch where they differ, goes to future-work.md.
* docs: basic authentication sends the password unaskedtv38 hours1-1/+4
| | | | | | | Review 4, 1269's note: the node challenges for a password (`WWW-Authenticate`) only what isn't a browser, by `Sec-Fetch-*` or a `Mozilla/…` user agent. A client calling itself Mozilla that waits for a challenge before sending its password would get none: api.md says to send the password with the request, as `curl -u` does, and why.
* docs: how long a broken path takes to see, with PINGs taking turnstv38 hours1-1/+5
| | | | | | | Review 4, 1258's note: the glossary says that a path whose way out broke while the peer's PINGs still come is found by this node's own unanswered PINGs, which with the turns come every one to two intervals, up to twice as long as before, and what still finds it at once.
* docs: what the request log carriestv38 hours1-1/+4
| | | | | | | Review 4, 1257's note: measurements.md now says that no endpoint takes a secret in its query, but every path read or written is in the request log, and paths are data here, so the lines go wherever the node's log goes; and that a user who may set the node's log filter may turn them on.
* remake: a backup's epoch as its snapshot names ittv38 hours1-16/+27
| | | | | | | | | | | | Review 4, appendix: a document loaded from a backup was taken to be on the largest generation `_continuum/epochs` held as it stood, verified at its only root. In a swarm without a policy a member's own change could plant `_continuum/epochs/99`, and a backup then failed to load until it was removed. The generation is now read at the document's only change without dependencies (its snapshot writes its own entry and carries the earlier ones), as the rest of the protocol reads epoch metadata; the genesis change writes none, so a first-epoch backup is genesis whatever was planted. The test plants one.
* access: a key's comment 256 bytes at mosttv38 hours2-3/+18
| | | | | | | Review 4, 1250's note: a key's comment was kept as given, unbounded but by the request limit (2 MiB), in protocol data every node holds and the dashboard shows. It says whose and where the key is: at most 256 bytes now (`sshsig::MAX_COMMENT`), a longer one refused with the key.
* docs: how signatures travel, as they do nowtv38 hours1-2/+10
| | | | | | | | | | Review 4, appendix: api.md's Signed changes said a node passes on each node's latest signature (true of deltas, not of messages carrying changes) and that every change arrives vouched for. It now says what a message carries (its changes' signatures, each run's latest past 4096 and for a delta), that a node signs each of its runs' latest unvouched change at start and when its document is swapped (1277), and the two cases that still arrive unvouched (a change vouched for only by a refused one, postponed; more than 4096 runs writing in an epoch).
* signatures: a listed signed path takes `*`, as everything else doestv38 hours1-3/+16
| | | | | | | | | | Review 4, appendix: whether a write is signed because `_continuum/access/signed` lists its path compared the listed paths by prefix (`signatures::related`), so a `*` there was a literal key, while api.md says the list holds paths as capabilities name them, which take `*` since 1259, and signed values already read the same list as patterns (`envelope::reaches_signed`). A listed `metrics/*/alerts` signed nothing. It compares by `access::agree` now: within or above the pattern, `*` for any one key.
* screen: that a policy was held kept across a swap of the documenttv38 hours2-4/+8
| | | | | | | | | | | Review 4, F12: `Screening::of` started screening over when the document was swapped (a drop, a switch, a rejoin), the root pin alone carried on, so whether the document had held a policy (`policed`, 1211) was forgotten there too, not only across a restart as documented: after a drop with the policy removed meanwhile, late changes written under it went in unjudged again. It is carried on now, as the root pin is; what belongs to the old document (changes waiting, refusals, drops held) still starts over. access.md says so.
* api: a stream opened by password or a proxy's header ends with its usertv38 hours3-44/+85
| | | | | | | | | | | | Review 4, F11: only streams answered to a token ended with it (1273); one opened by password (basic authentication) or a proxy's header went on after its user was disabled or removed. `streams_end` now wraps those too, by the user `basic_auth` leaves on the answer: such a stream ends once the document no longer has its user, enabled (`token::user_taken`, the part of a token's check about its user, which `still_taken` uses too), looked at as the stream begins and as protocol data changes. No expiry: a password has none.
* sync: a write between signing and making a message signed tootv38 hours2-9/+25
| | | | | | | | | | | Review 4, F10: a sync message signed the node's latest change under the document's write lock (`sign_sent`), then was made under its read lock: a write committed in between went in the message unsigned, and under a policy requiring signatures waited at the peer until the next signed change came with it, or a resend. Now the message is made under a read lock taken after signing, and if a write came in between (`AppState::sign_due` again under that lock), signed again: a few times at most, past them it goes as before, its signature with the next message.
* api: a stream listens for its token's end from before it is answeredtv38 hours1-17/+33
| | | | | | | | | | | | | Review 4, F9: a stream answered to a token subscribed to the document's changes only when its body was first polled, and looked at the token only after a change came: one revoking it that came between the request's check and the subscription (a node applying what it was behind on) went unseen until the next change of protocol data, or the token's expiry. Now `streams_end` subscribes before the answer is made, and the token is looked at once as the stream begins (`refused_now`, the document and this node's ended sessions), then with every change of protocol data as before. A request that isn't answered with a stream drops its subscription with the answer.
* roots: only the seeds' roots end the wait for themtv38 hours3-6/+28
| | | | | | | | | | | | | | Review 4, R2 partly (F8): a joining node waited for "each of its seeds" to name the swarm's root as a count, of every admitted peer that named one. Node identities are self-made and admission is the swarm key: a member answering a joiner under as many identities as it has seeds, naming its own root within the wait, made the joiner pin it before the real seeds answered, and refuse them after as another history. Now the seeds are the nodes proven at the seeds' addresses (`roots::seed_found`, as `bootstrap::resolve_seeds` finds them), and only their roots count toward the wait and start its clock. Another peer naming a root still disputes it (no root while they differ), so such a member can keep a joiner from joining, as any member can, but not hand it another root. access.md says so.
* access: an ended session recorded until the login's endtv38 hours2-2/+14
| | | | | | | | | | | | | Review 4, F7: `DELETE /auth/session` recorded the session as ended until the `session_exp` of the token it came with. A token handed to another member's dashboard has its `session_exp` cut to its own end (five minutes), and so has the session that dashboard keeps with it: logging out there recorded an end minutes away, the next remake's snapshot left it out, and the original browser's tokens of the same session, refreshable for days, were taken again. A token now keeps the login's end as it logged in (`Claims::login_exp`), which no refresh changes, and an ended session is recorded until then (or the token's `session_exp`, if later, as for a token from before the field).
* remake: earlier epochs' metadata carried as built, not as it standstv38 hours1-5/+23
| | | | | | | | | | | | Review 4, F6: a snapshot judged which earlier epochs' metadata to leave out as the base epoch's snapshot built it (1254), but copied `_continuum/epochs` from the base as it stood. In a swarm without a policy, a member's rewrite of an entry (its window, kind, clock, translation) went into the next epoch, and was "as built" there; an entry it added was carried and never left out, one it removed not carried. Now `_continuum/epochs` is copied from the base epoch's snapshot change too (`as_built`), then pruned; the first epoch's base carries none. The test adds a generation and narrows a window after the snapshot: neither reaches the next one.
* access: a shallow read shows only the way through where the user reads partlytv38 hours2-7/+39
| | | | | | | | | | | | | Review 4, F4: `?shallow=true` kept every entry the user may at least pass through, and a shallow read inlines short scalars' values with their types and sizes. A scalar at a level a `*` passes through (a capability on `data/*/public`, an entry `data/count: 42`) showed its value though no capability covers it; before `*`, only a scalar where a capability expected a map did. Now an entry the user may read only partly is shown only as the way through it is: a map, with no more than its type (not its size, the number of its keys); anything else there (a scalar, a list, which a deep read leaves out too) isn't shown. Entries it may read whole are as they were.
* signatures: up to 4096 in a message, each actor's latest past themtv38 hours3-6/+9
| | | | | | | | | | | | | | | | Review 4, F5: a delta, and a message whose changes held more than 256 signatures (a whole document pulled, of any real history), carried each actor's latest signature, the 256 that became latest last. An actor is a node's run, so past 256 runs writing in an epoch, some actors' changes arrived unvouched at the puller under a policy requiring signatures, waited, and every resend carried the same 256: a joiner never finished. A message now carries up to 4096 signatures (`MAX_PER_MESSAGE`, as many as wait for their changes, `MAX_PENDING`: 384 KiB at most), all of its changes' up to there, each actor's latest past it and for a delta, and a receiver takes as many (wire version 47). An epoch with more than 4096 runs writing would still leave some unvouched in such a transfer, which the constant says; sending each actor of the transfer its own latest would need the actors of a delta's changes, which only rebuilding their metadata gives.
* signatures: the latest unsigned change of each own actor signed, at start ↵tv38 hours3-39/+58
| | | | | | | | | | | | | | | | | | | | | and after a swap Review 4, F2 (the honest case) and claim 2: a node signed at start its own changes at the heads that nothing vouched for (1216), and after a swap only its current actor's latest. An own change left unsigned can sit below the heads: a drop (`drop_taken`) takes away a signed change and keeps an earlier unsigned one, and the node writes on under a fresh actor, building on it; or a node's change is built on by another's concurrently with the signed one the drop took. Nothing signed it again, and under a policy requiring signatures every node pulling it (a joiner) waited for good, and for everything built on it. Now the latest change of each of the node's actors (`RemakeState::own_actors`, the current one among them) that no signature vouches for is signed (`Signatures::sign_own_actors`), at start and whenever the document is swapped (a drop, a switch, a rejoin), the old `sign_own_heads` and `sign_own_latest` going. One signature per actor vouches for all its changes before it. The case of a member whose signed change the rules refuse, vouching once for its earlier ones, is postponed (future-work.md, next).
* transport: a pushed change written out by its size, never its bytestv40 hours1-2/+39
| | | | | | | | | | | | Review 4, F3: the console writes each message out with `{:#?}`, and `SyncBody` derived `Debug`, so `Changes` (changes pushed as they are) printed every byte of every change. Pushes are the small messages, and Automerge deflates no change under 256 bytes: a short value's path and value went into the console in clear, which any user with any capability reads (the page's `messages` events, and since 1266 a subscribed node's `messages/own/{seq}` too), paths it may not read included. `SyncBody` now writes itself out with sizes for the changes, as `SyncPayload` does for an Automerge message; heads and a delta's spec stay as they were.
* access: a token's limit narrows what an endpoint takestv40 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 ittv42 hours7-15/+126
| | | | | | | | | | | | | | | | | | | | 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 sessiontv42 hours10-11/+188
| | | | | | | | | | | | | | | | | | | | 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 includedtv43 hours2-3/+21
| | | | | | | | | | | | 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 betweentv43 hours5-24/+42
| | | | | | | | | | | | | | | 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 authenticationtv43 hours3-23/+62
| | | | | | | | | | | | | | | | | | | | 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 days4-13/+90
| | | | | | | | | | | | | | | 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 days7-18/+102
| | | | | | | | | | 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 days5-36/+90
| | | | | | | | | | | | | | | 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 days6-33/+88
| | | | | | | | | | | | | | | | 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 days5-36/+73
| | | | | | | | | | | | | | | | | | 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 days2-5/+85
| | | | | | | | | | | | | | | | | | 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 days4-6/+70
| | | | | | | | | | | | | 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 days2-1/+35
| | | | | | | | | 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 days3-5/+64
| | | | | | | 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 days3-6/+124
| | | | | | | | | | | | | | | | | | | | | 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 days4-6/+25
| | | | | | | | 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 itselftv2 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 kepttv2 days8-93/+231
| | | | | | | | | | | | | | | | | | | | | 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 dashboardtv2 days2-7/+17
| | | | | | | | | | 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 legendtv2 days2-35/+51
|
* dashboard: an entry's menu left of the fingertv2 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 alonetv2 days2-59/+32
| | | | | | | | 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 boxtv2 days2-25/+197
| | | | | | | | | | | | | | 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 fromtv2 days3-29/+70
| | | | | | | | | | | | | 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.