| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A remake that names all users names those that count in its
proposal's history, and still do at its base: the list is fixed with
the proposal, so those who vote see it, and a user brought in while
the votes run isn't on it. A fork's voters go the same way.
And what reading the design from end to end found:
* a remake does two things, not one: it throws history away and names
who votes next;
* nothing plain is written but a node's member entry and its records
of proposals, said where namespaces are, too;
* a node doesn't sync under a policy it doesn't know, whatever its
version says;
* two sides that only resolve differently show by their stamps;
* on the wire a federated swarm's handshake says which epochs a node
keeps, and a version speaks the one before its own: "no message
changes" holds for a swarm that isn't federated;
* three more things assumed: what was seen for long kept as the node
goes, the cost of a digest with every stamp, two wire versions
spoken at once.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
What the maintainer asked of the design:
* Who votes is named by the remake's proposal: all users at its base,
as recommended, or a list. So those who vote say who votes next,
and nobody votes by a rule of who created it. What that costs is
said with it: every voter named counts toward what a decision
takes, and all users is whoever brought users in.
* What a policy no longer covers is uncovered at once where the
decision holds: shown aside by every node, taken away by its user
only, changed by nobody, and dropped once it has been seen there
for `uncovered` (a number; 0: at once). Within an epoch a rule
moves nothing in the document; the remake moves it.
* Being seen takes nothing but a node that is up: its stamp is
renewed about daily whatever else is written. A node left running
keeps its voter present; naming the voters deals with that.
* A release of a vote is best effort.
* Continuum changing under a swarm: versions agree by what they
compute, not by a number. Every stamp carries the digest of the
snapshot its node would build there; a remake is taken up by the
ID each node built; a node checks itself against the swarm before
it writes with a new version; a change of the rules is a change of
policy, and a node knows a policy by everything in it; recorded
vectors every version passes. What can't change, what changes
freely, and when it is a fork.
* Who may connect is outside the design; the swarm key is a note.
The attacks are 84.
future-work.md: several roots, present by the user's own signature, a
guide to moving to another swarm.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
What the maintainer asked of the design, and what it changed:
* Lines. A user is in while it has a line: a chain of creations and
vouches from the root. When its creator goes, one that another
vouched for stays without doing anything. The witnesses of its
claims are the users on the lines it stands on: its creation's, and
those of vouches it accepted (`accept`), so nobody gets a say about
a user's names by vouching for it unasked. Why only its signer
takes a vouch back is said where unvouching is.
* Lengths of time are read between two stamps of one node, with what
that node had seen at each: a voter is absent once a third of the
voters saw it silent for long, a user gone once they saw it out. No
clock is set against another, and why a third.
* Decisions. A proposal is decided as soon as the nodes of two thirds
of the voters hold its votes: at once, not at the end of a window.
Any number are open at a time, within a limit a voter. Of the
decisions of one matter the last holds, by which proposal was made
in sight of which, so two decided at once stop nothing. A remake's
base is named by its coordinator, a node of the proposer.
* The policy changes by a decision, as a whole, with no remake: a
remake throws history away and does nothing else. What a policy no
longer covers doesn't count, and a remake keeps it aside for an
epoch or drops it, as the policy says. Going apart by a fork stays
for those a decision leaves behind.
* What a remake leaves out, so that the document's size goes by what
stands: a release is a statement in its epoch, the remake deletes.
* Names are written as they are used, in the document and in the
rules alike, and compared by their segments: structure, not
meaning. A rule may leave a name to the voters to give.
* Who vouched for whom changes nothing in who connects to whom: a
node keeps its seeds and its user's other nodes as neighbours.
The attacks are 83, the stages fifteen.
future-work.md: guides to write (keys, the root, starting a swarm,
voting, a registry), policies merged.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
What the maintainer answered to the design's last two questions, and
what follows from it:
* The fractions are numbers of the policy like the others, changed by
a decision to any value: the share of the voters present a decision
takes (two thirds recommended), the share of all of them (half), and
the share whose stamps make the swarm's time and a user gone (a
third). What is promised of decisions is argued for the recommended
ones; Why it holds says what other values carry.
* Refusing a decision: a node follows every decision that counts
unless its operator has it refuse, by bounds on the policy's numbers
or by hand. It then moves to the epoch built at the decision's
history without it, which is the same for every node that refuses
the same decision: those who think alike go on together without
arranging it. While a proposal's votes run, a node whose bounds it
breaks says so. A remake or a change of policy is refused by
staying.
* A decision gives a user name with its namespace, as designed.
* No open question is left. The options not taken that the questions
listed are among the roads not taken: first users voting as soon as
created, voters let in without a remake, other reaches of an
unvouch, a gone user coming back, first keys by those who vouch,
a registry of only what is signed, numbers the design fixes.
The attacks are 78; the stages name them by their numbers.
future-work.md: bounds on more than numbers.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
What the maintainer answered to the questions the design had left:
* Every quantity of the design is a number of the policy, in one
table with what the design recommends (the times, the limits, how
much a remake's base may lack, how long the root's node lets a claim
wait). The genesis policy sets each, and a decision changes any to
any value: nothing constrains them. The fractions aren't numbers.
* While a user is out, the voters free its names or give them to
another by a decision, whatever `gone` says; a policy without `gone`
has the names wait until a vouch or a decision.
* Nobody is put out by a vote, said where unvouching is, with what
there is against a user that harms within the rules.
* A node keeps the epochs it left for as long as its operator says,
`archiveRetention` at least, and says which; so a node coming back,
or one joining from the genesis root, walks every epoch where an
archive is within reach, and shows where the walk doesn't end at its
peers' epoch.
* Settled, and listed so: who votes, from where an unvouch undoes,
gone for good, first keys by a decision, what the root holds
unsigned. Two questions remain: whether the fractions are numbers
too, and whether a decision gives a user name with its namespace.
future-work.md: an archive of every epoch.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
What the maintainer answered to the design's open questions, in the
design:
* The numbers. The limits and the times are the policy's numbers, set
in the genesis policy and changed by a decision: 64 keys, 64 nodes
and 256 addresses a user for a start; `window`, `absent`, `gone`,
`archiveRetention`. A lowered limit takes nothing away.
* A user nobody vouches for is out, and its names wait for it: nobody
else claims them. Out for 30 days, as the stamps of a third of the
voters show, it is gone: its names are free, and no vouch brings it
back. So whoever relied on a vouch that an unvouch or a cut took
back has that long to find another, and loses nothing meanwhile.
* First keys by a decision. The voters are above every user's first
keys: they give a user that lost them new ones, with a cut for the
old. The promises say so, and what isn't promised: a name against
two thirds of the voters.
* What a decision is made of (a proposal, a vote, its release, the
decision) is taken in any node's change, so a user without a node
votes at whatever node it reaches, or by a vote carried to one. Such
a user names another party's node to carry for it.
* Coming back after remakes: by the archives on nobody's word, epoch
by epoch, as far as peers keep them; else by an ID the operator
gives, never because peers advertise an epoch. A statement is
carried over from the epoch before only; older ones are quarantined
for the user to sign again.
* Resolving reads the decisions first: nothing below changes them.
* The questions that remain are put with their options: who has a
vote and from when, whether the voters put a user out against those
who vouch for it, from where an unvouch undoes, whether gone is for
good, the price of first keys by a decision, and what the root
holds unsigned.
The attacks are 76; the stages name them by their numbers.
future-work.md: barring a user by a decision, and voting made easy for
a user without a node.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
federation.md designs what the krebs registry needs of continuum: a
swarm several parties keep, any of them possibly malicious. Nothing of
it is built. It is a mode a swarm is started in: one that isn't
federated stays as it is.
* The parties, what an adversary can do, and what is promised to an
honest node: only valid changes; the same document and the same
resolved registry for the same changes; values signed by their users;
one holder per name; a claim nobody takes once the root's node saw
it; no waiting for anyone to write; and, with fewer than a third of
the voters present malicious, decisions by two thirds of them. And
what isn't: one history under a partition, a name never contested,
decisions against a third, against keys stolen later, against a
malicious voter on both sides of a long partition, or with half the
voters gone; a swarm no forked actor splits.
* The design, on the strict policy. The policy is in the root. Users
come by statements that only add; a user is its creation, its keys a
tree, its nodes those it names. Vouching and the line; unvouching,
with what exactly it takes back. A namespace per user. Names of
declared kinds claimed, with rules and limits as data, some kept for
a user without a claim. Of two claims out of each other's sight, one
is late where its own side can be shown to have seen the other
first; where that leaves not exactly one, the name is contested, and
a release gives it up. Cuts: what a key signed outside a set of
changes stops counting, whenever it shows up; a key is cut off by one
above it only. Bans change what those who follow read, not what
anyone takes.
* Decisions: votes signed by the voters, over days, by numbers the
document holds and a decision changes. Who votes is in an epoch's
root change: the root alone at first, then, with each remake, the
users a root created, so nobody fills the votes without two thirds
of those there agreeing. A time read from the document, by which
those away for long don't count; a decision taken up by the voters'
nodes while its votes run, and counting with two thirds of those
present and more than half of all, so that honest voters never make
two however long a partition lasts. The
root's seat given anew by a decision, and what a root finds when it
comes back. Remakes: the base is the decision's history, every node
builds the snapshot from the registry as it resolves, statements are
carried over, and what counted at the base is final. A change of
policy as a fork each operator follows or not, as Bitcoin changes
its rules; it may name who votes on its side, and is the way out
for those a decision leaves behind.
* What every node judges and how the registry resolves, how a partition
shows and how a side comes back, links along the line, tokens of
one's own party only. Why the promises hold, the roads not taken,
what members are trusted with then, what is continuum's and what the
application's, 69 attacks with the tests that show them, the
registry's layout with moving in and writing out, fourteen stages,
and ten open questions with a choice for each.
future-work.md gets what the design leaves.
|
| |
|
|
|
|
|
|
|
|
| |
The document file's first line names its format; each record's first
byte its kind. A version that doesn't know a kind refuses the file, as
it does a format it doesn't know. With the Closed and Opened records,
the format is continuum-document/3, so a version knowing only 2 refuses
a file it can't read by its first line. Files of 2 are read as of 3:
the same records, Closed and Opened where a version knowing them wrote
them.
|
| |
|
|
|
|
|
|
|
|
|
| |
The document file's format names itself in its first line
(continuum-document/2) and each record's kind in its first byte. A
record of a kind the reading version doesn't know makes the file
unusable: the node doesn't start on it unless told to move it aside
(--force badDocumentFile). lifecycle.md says so: a file one version
wrote is read only by versions knowing every kind of record in it. The
format's comment names the Opened record a node appends as it starts on
the file, which undoes the clean stop's mark.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Ending a session records it in the document until the login's end
(login_exp), so every token of the session, refreshed or handed over, is
refused until none can live any longer. A login may ask a lifetime of
its own (POST /auth/login with lifetime): its session ends with that
token (session_exp), as a refresh never goes past it. Its login_exp is
now that end too, so ending its session records it as long as one of its
tokens can live, not for the node's whole session lifetime. A token
refreshed with a lifetime (one handed to another member's dashboard)
keeps the login's end.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A node receiving a change of the actor it writes as, one it doesn't
hold, writes on as a fresh actor (another node writes as it, from a copy
of its files). Where the other node made it at a seq this node made a
change at too, the change was taken to Automerge, which refuses the
whole batch (duplicate seq): the message failed, and with it everything
else it carried. Signatures::forks found it forged only where a
signature verified a change of the actor at that seq, and this node
signs its own latest change only as it sends it.
forks now also finds a change forged where its actor is the one the
document writes as and the document's latest change of it
(get_last_local_change) is at its seq or later. apply_bytes switches to
the fresh actor after judging the batch, not before, so the check still
sees the actor; also where judging fails.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A sync message larger than the threshold for its peer (16 chunks, lower
on a slow link or after a message to it failed whole) goes as a delta:
the peer fetches its changes in chunks, from any node holding them. Its
signatures, each writer's latest with changes in it, went in the
envelope offering it: up to 65536 of them, 6 MiB, in one message, the
size the delta exists to avoid. Nor did an Automerge message count its
signatures (one per writer in a whole document pulled) towards the
threshold.
A delta's signatures are now a blob of their own: the offerer packs and
keeps them (Blobs::keep, the latest 64), and the spec names them by
BLAKE3 hash and size (DeltaSpec::signatures, None where no signature
vouches for its changes). They are served as content (BlobId::Content),
so their hash checks them. The receiver fetches them from the offerer
alone, as another node holding the changes holds signatures of its own,
before the changes, and offers them under the write lock just before
judging the changes, so they wait for them as before. The envelope of a
delta carries none. An Automerge message's signatures count towards its
size: past the threshold, it goes as a delta.
Wire version 49.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A received signature of a change the document doesn't hold yet waits for
it (Pending), checked once the change comes. A delta's signatures come
before its changes, so they wait for the whole download. Kept one per
change, the last that came, a member could send a signature that
doesn't verify for a change it knows is on its way and put the honest
one aside: the writer then went unvouched for until its changes came
again with their signatures.
Pending now keeps up to three different signatures of a change
(CANDIDATES): the first that came, and the latest others. Ed25519
signatures are deterministic, so every honest copy is the same bytes and
counts once: one sent first stays, one sent after stays unless more
others follow it. Once the change comes, offer and vouch_batch check
them in turn, the first that came first, up to the first valid one,
within the bound on invalid signatures (MAX_INVALID). MAX_PENDING counts
signatures, not changes.
|
| |
|
|
|
|
|
|
|
|
|
| |
Received signatures of changes the document doesn't hold yet wait for
them, up to 65536 (MAX_PENDING). Each Signatures::offer, one per
received sync message, looked up every waiting one's change to find
those that came meanwhile.
The pending signatures now remember the document's heads at the last
look (Pending::scanned, Pending::arrived): while they stand, the
document holds nothing more, as it only grows, and offer looks up none.
|
| |
|
|
|
|
|
|
|
|
| |
A stream opened with a token ends once refused_now refuses it. In a
swarm without a registry, a session ended on this node is kept in memory
(AppState::ended_sessions), and the streams waiting on session_ended are
woken (notify_waiters). credentials_end now makes the Notified it waits
on before each refused_now, the first one included, and not after: a
session ended between a check and the wait is seen at once rather than
at the next change to protocol data.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A sync message carries up to 65536 signatures (MAX_PER_MESSAGE), and the
receiver checks, under the document's write lock, each one of a held
change that would vouch for more of its actor's changes than the one
before: about 30 µs each. A member sending that many signatures that
don't verify cost the receiver about two seconds of writes per message.
Signatures::offer and vouch_batch now stop checking after 64 found
invalid (MAX_INVALID) and drop the rest unchecked, with a warning. A node
forwards only signatures it verified, so an honest peer sends none.
offer counts those a message brings apart from those that waited for
their changes, which came from any peer. Signatures::vouch says which it
found (Vouched): more vouched for, nothing more, or invalid.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
With --keep-actor, a node on a document file that ends with a clean stop
writes on as the actor the file names. Two nodes started on copies of
one file (a copied data directory, a restored disk image while the
original runs) both do, and both make that actor's next change number:
peers holding one refuse the other as forged, and everything built on
it.
screen::apply_bytes, the way every received change comes in, now looks
for a change of the actor the node writes as that the node doesn't hold:
only another node can have made it. The node then writes on as a fresh
actor at once, logs a warning, and its callers add the actor to the
node's own (RemakeState::add_own_actor), which the document file's
header follows. The change is judged as any other. lifecycle.md says not
to start a copy of a node's data directory with --keep-actor, and what
the switch leaves: what both wrote before stays refused where the
other's came first.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
With --keep-actor, a node on a file whose last record is Closed writes on
as the actor the header names, which takes the file holding every change
of that actor. At a clean stop, the node's own workers go on writing
until it exits: its alive stamp in the member list, votes, a
coordinator's commit. One committed after Closed was appended, and sent
by the still-running sync, would be a change of that actor the file
lacks, and the next start would make another change with its number.
swarm::document_file::close now takes the document's write lock, marks
the file at the heads it sees there, and sets a fresh actor before
letting go: what the node writes after is no change of the marked actor.
A record the file worker appends after Closed (such a write, flushed)
leaves the file unmarked, which starts a fresh actor next time.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
At start, a node on a document file appends an Opened record before it
writes anything, so that a Closed record before it no longer says the
file holds every change of the actor it names. It does so now before
deciding which actor to write as, and DocumentFile::open says whether it
could: where the append fails (the disk full, permissions), the file
still ends with Closed, and later appends likely fail too. A crash, or a
clean stop whose last append failed (close then sees the file behind and
writes no mark), would leave that Closed standing for a run the file
never got. So with --keep-actor, the node writes on as the actor of
before only where the file was marked as opened; else it writes as a
fresh actor and warns.
|
| |
|
|
|
|
|
|
|
|
| |
look with
Looking at a node (its figures, peers, dashboard: Need::Observe) takes
any capability, a token's limit applied. Caps::is_empty is now true where
no capability of the user meets one of the limit's (Capability::meet), as
well as where there are none: a token limited to the member list, for a
user who may only read data/eu, may not look.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A token's limit narrows every endpoint but those under /auth, which any
registered user may call. Among them, PUT and DELETE /auth/keys/{id}
change the keys the user logs in with, and POST /auth/logout ends every
token of the user. With the token a dashboard hands to another member's
(the member list, read, five minutes), its holder could add a key of its
own and log in by it as the user, with everything the user may do.
These now take a password or a token without a limit
(Need::Credentials, refused where Caps::limited). A narrowed token still
refreshes within its limit, says who it is, lists the user's keys and
ends its own session (DELETE /auth/session): a dashboard opened from
another member's logs out with it.
|
| | |
|
| |
|
|
|
|
|
| |
With the other options of the document file: what it does, what it saves (a writer per
node and per crash or drop, rather than per start), and what it must not meet: two
nodes started from copies of one document file, or a node from an older copy of its
own, which would write as the same actor.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
| |
`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 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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|