| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
| |
Each change taken but not yet applied got its own set of the heads it
is judged at, built in an ordered set and copied into a vector, and
kept so for those building on it. Most build on one change alone and
are judged at its heads: share them instead (reference counted), and
build a set only for a change with several dependencies.
|
| |
|
|
|
|
|
| |
Screening copied every change it took, and every one left waiting for
a signature, bytes and all, from the batch it had ordered and then
dropped. Move them out of the batch instead; only a refused change,
recorded with why, is still copied.
|
| |
|
|
|
|
|
| |
in_order is about a seventh of screening in the profile, most of it
hashing change hashes: each change went into and out of three maps,
and each dependency into two. Look each hash up once, in a map from
hash to place, and keep the rest by place in vectors.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The profile of bench_screening puts screening at about 21% of the
samples, half of it Automerge applying the changes. Of the other half,
reading the policy for every change taken is the largest part (3.4%):
reaches_signed reads it, and what users sign, to tell whether the
change must go in before the next is judged.
Read them once, and again only after the document took the accepted
ones; a change to the policy or to what users sign is relevant, taken
at once. A change touching nothing reaches nothing.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
With the policy's reads kept, judging a change still costs about one
read (some 3.5 µs), and so does judging without a policy: what is left
is At::new, which asks get_change_by_hash for each head. Automerge
builds the change again from the document's operations to answer, at
a cost growing with its history. Screening asks it again for every
candidate and every dependency, and sync for heads.
Ask by the missing dependencies instead (changes::holds), which look
the hash up: one the document holds is not among them. A change queued
in the document, waiting for its dependencies, isn't either; but then
some are missing, and only then is it asked the slow way. Every check
of whether a document holds a change goes through it.
|
| |
|
|
|
|
|
|
| |
The breakdown judges at the heads after 400 changes. Whether a read
at given heads costs more in a document with a long history decides
what a read the rules don't keep costs a node with a big document.
For the workloads of more than 10000 changes, break it down a second
time, judging the last 1000 at the heads after the rest.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
bench_screening shows judging at about 17 µs of a change's 25: some
two and a half reads at its heads (the policy, bans, its node's member
entry, whether there is a registry, the sealed declarations), each
costing as much as working out what the heads cover. Screening judges
most of a batch at the same heads, and those reads are the same for
every change judged there.
Keep them (rules::Reads), by node and by user where they depend on
one, while the heads stay the same; screening keeps one for a batch.
What the heads hold doesn't change as the document takes changes, so
nothing goes stale but by other heads. The benchmark breakdown shows
judging so and afresh.
|
| |
|
|
|
|
|
|
|
| |
bench_screening tells what screening costs in all, not where. Print,
per change judged, what decoding it, placing its operations
(inspect::Inspector) and judging it (rules::judge) cost; of judging,
what reading the policy costs, and one read for scale. Judged as
screening judges most changes: at the heads after the first ones,
whose maps flush the batch.
|
| |
|
|
|
|
|
|
|
|
| |
A write every node would refuse is rolled back by forking the document
at the heads before it, under the write lock, at a cost that grows with
the document's history. A user could ask for such writes in a loop.
Count each user's refused writes over the last minute. Past
--refused-writes-per-minute (default 10), its writes are answered 429
without being tried, until the oldest refusal leaves the minute.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N7, the care it raised: a joining node's empty document took
as the first epoch's root whatever dependency-less change came first,
from whichever peer it joined through; the first epoch's ID is all
zeros, so nothing pinned it. A remake's epoch has its ID, the snapshot
change's hash, which the rules hold its root to.
* Every node names its epoch's root in HELLO and HELLO_REPLY (the
epoch's ID for a remake's, its genesis change for the first; wire
version 45). A node whose document starts from another root of the
same epoch refuses the handshake, logging it: another history under
the swarm's ID, a forged root or a genesis started again.
* A joining node with an empty document takes as its root only the one
it was given (--genesis-root, new), else the one its peers name, and
none while they name different ones (access::screen::RootPin, kept
in Screening across documents): one peer hands it another root only
if it is all the joiner hears, or never, with --genesis-root. A root
refused is listed with the refused, what builds on it with it.
* The genesis node logs the root (`The swarm's root`); GET
/admin/epochs shows it (`root`).
A genesis started again under a remembered swarm ID used to merge with
the nodes of it still running; they now refuse it (another root), and
the docs say to join them with seeds instead.
The epoch's identity stays as it is (generation 0 all zeros): records
and epoch metadata name it so, and every comparison of epochs with
them.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N7: a node screened received changes only while its own
document had a policy it enforces. A joiner's empty document has none,
so its first sync, as much history as the node it joins through chose,
was applied unjudged, and under signatures required unvouched; the same
for any node while the policy's own change arrived with later ones.
Sync messages, deltas and downloaded changes are now always screened,
each change judged by the policy at its dependencies (1135), so the
verdicts are the swarm's. While neither the document nor the changes
hold a policy (none touches a "policy" key: inspect::touch_key), only
an epoch's root is judged and nothing is placed: bench_screening, after
1188, measured placing everything at about as much again as applying
(564 ms + 543 ms for 100 000 changes), judging by the rules at about
five times (+2.5 s), which a document with a policy pays anyway.
access.md no longer lists the joiner's first sync, nor nodes agreeing
only as the policy reaches them (judged at the dependencies, they
agree), as left to do; changes::load_delta is for tests now.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N9: the API checked a write's capability on the path asked
(servers/list/3/name), the rules on the list it is in (servers/list):
a user whose capability lay inside a list had every such write pass the
API and refused by the node's own judgement (403, after rolling the
document back).
inspect::placed places a write's path in the document by the rule the
inspector places operations by (Place::child: cut after the first list
or text on the way), and the store's writes check capabilities there.
One rule for both; a test checks placed against the paths the inspector
finds in the change such a write makes.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
bench_screening showed screening growing quadratically: 1 000 changes
took 29 ms on top of applying them, 10 000 took 2.8 s, and 100 000
didn't finish in minutes. in_order placed changes in rounds, each
taking those, in hash order, whose dependencies were placed: a chain
received in one delta, its hashes in no order of their own, took about
as many rounds as changes, each over all of them.
It now counts, for each change, its dependencies among those received
not placed yet, and places the smallest hash of those with none left,
then those building on it as their counts reach zero (Kahn's
algorithm): n log n, and still the same order on every node, whatever
order the changes came in. One with a dependency neither in the
document nor among them, and what builds on it, still wait.
|
| |
|
|
|
|
|
|
|
|
|
| |
Review 2, N7, measured first: a node whose document has no policy (a
joiner's empty one) applies what it receives unjudged. Screening it too
would cost finding every change's paths and judging it. bench_screening
takes 1 000, 10 000 and 100 000 changes into an empty document, as one
delta brings them: unjudged, as Automerge applies them, against
screened (access::screen::apply), with no policy in them (each passes,
read at its dependencies, after its paths were found) and with the
policy their first change wrote (each judged by the rules).
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N2: nothing checked that a strict remake's base wasn't strict
already. In a strict swarm the registry, the policy and the bans change
only as a user who may change them signs; a strict remake committed by
a majority of members, each with keys of its own, could drop any of
them (unban, remove managers, empty userSigned and the sealed
declarations), and the honest minority would follow.
A node now takes a strict remake (proposing or voting) only in a swarm
not strict yet: turning strict is done once, by a remake; after it, by
signatures.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N8: a node with one neighbour (a swarm of two, a leaf) never
hears a second member name the change: its big drop was held for good,
an error logged at every REFUSALS. It now waits only where it has
another neighbour than the one telling it; with one, one word is all
there is.
access.md also says what the wait is: it guards against one peer, not
two members saying the same (any node with the swarm key registers),
and never makes a node drop what its own rules take.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N3: 1150 let any member write an abort decision once a remake
had expired a grace period ago, judged by the change's own time, the
writer's word. A member could race the coordinator's legitimate commit
with an abort dated as it liked; both taken, Automerge shows
one, and nodes that acted on the commit split from those that didn't.
The two writers of such aborts are operator requests whose endpoints
already need write on _continuum/remakes: aborting an expired remake
(DELETE /admin/remakes/{id}) and superseding one (POST
/admin/remakes?force=supersede). Both now commit as the user who asks
(the proposal and its vote with them, for a supersede), which the rules
take as one who may write the decision. The clause judging an abort by
its time goes, and with it the time the rules carried. An expired
remake is still aborted through any member, by an operator.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N1: a strict manifest wasn't bound to its base. Any member
could propose an old, legitimately signed one again at the current
heads; nodes would verify it and vote, and every value at a signed
place changed or added since (a ban, a new user, a rotated key, a
changed userSigned or sealed declaration) would be deleted, nothing
after the base being left to replay.
The manifest now names the epoch and the heads it was made at, and a
node takes it (proposing or voting) only for a remake of that epoch
with exactly that base. GET /admin/strict-manifest makes it so;
continuum-signed strict proposes at those heads already.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N12: a strict remake kept only the values at the signed
places its manifest names, and copied what holds them as it was. A
member that replaced _continuum/users by a scalar before the turn left
the strict epoch without a registry, where any member may do anything.
The holders of the registry, `_continuum/access` and the bans are now
made anew as maps, with only what the manifest keeps copied into them;
and a manifest keeping no user is refused (at proposal, vote and build
alike), as a strict swarm without one would trust every member.
|
| |
|
|
|
|
|
|
|
| |
Review 2, N10: the judgement before a change leaves (1140) and a
replay's (1141) skipped changes when the document *with* the change had
no policy it enforces. A write removing the policy, or setting one the
node doesn't know, then left unjudged, while receivers judge it at its
dependencies, where the policy was enforced. Both now read the policy
at the heads before the change, as receivers do.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N6: finding 2's class survived for two reads the frontier
didn't cover: whether a map a change makes is there (maps made on the
way, 1137; holders made where none, 1147, for any path), and
_continuum/votes, which `relevant` didn't list. A crafted pair, the
second making a map the first made, was taken by nodes receiving both
in one batch (judged at the frontier, where the map isn't yet) and
refused by nodes receiving them apart.
The screen now keeps the paths the accepted, not applied yet, touch;
a change making a map at one of them is judged after they go in. Votes
join what `relevant` lists. A crafted pair needs hand-made changes,
which the tests don't build.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N5: under a strict policy a user's signed PUT of its own
entry, keys changed (continuum-signed key-add), passed the API and
envelope::check's own_keys_only, and was then refused by the rules'
path check: _continuum/users/<name> wanted write on the registry, so the
node judged it as every node would and answered 403. 1168 worked only
without strict.
Under a strict policy the rules now take a write of a user's own entry
from that user, leaving what it may be to signed_values, which runs
envelope::check: an envelope by the user, with only its keys changed,
one left at least. Strict only: under another policy the entry isn't an
envelope, and the user would rewrite its own capabilities.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N4: replay_newest names the node the document's author once,
before its first group. Taking a refused group back forks the document
at the heads before it, and Automerge's fork has no author; when the
refused group was the actor's first change, the next one was committed
as seq 1 without the footer naming the node, refused as "it names no
node", taken back too, and so on: the whole replay in the quarantine
with a misleading reason.
The fork now names the node its author again. Test: the first group
refused (a capability revoked in the new epoch), the next replayed.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A strict remake takes a majority of members whose --strict-keys take
its manifest's signer: no node votes for one without, so
--follow-strict-remakes (1173) goes. A node that doesn't vote still
switches once the others commit, as with any remake.
So that an operator can tell why one fails: a strict remake aborted
for want of a majority (the voting period over, or a majority out of
reach) says in its reason (GET /admin/remakes/{id}, and the
coordinator's log) which members didn't vote, and that a member votes
for one only with its manifest signed by a key of its --strict-keys.
Each of those logs why itself (`Not voting for a strict remake`).
The sim a_strict_remake_too_few_take: only a takes the manifest; the
remake aborts, naming b and c.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Until a swarm is strict, the user a change names is its node's word, so
any member could have planted registry entries, policy, bans or
user-signed values as any user, plain or signed by a key it put there
itself; writing the strict policy trusted all of it as it was.
A remake of a new kind, `strict`, turns a swarm strict instead:
* Its content is a manifest (access::strict): the swarm, the strict
policy, and each value kept at the places values are signed, by the
SHA-256 of its JSON, signed by an operator (ssh-keygen -Y sign -n
continuum-strict).
* Every node builds the next epoch as compaction does, keeping at the
signed places only the values the manifest names, as they were at
the base, and writing its policy (RemakeKind::protocol, the one kind
that changes protocol data). Writes after the base reach the new
epoch only by replays, judged by the strict policy.
* A node votes for one only with its manifest signed by one of
--strict-keys, keys given out of the document; without any, for none
(logged once, `Not voting for a strict remake`), unless
--follow-strict-remakes. A coordinator proposes only what it would
vote for (403). Once a majority commits one, every node switches, as
with any remake: refusing to switch would only cut a node off.
* GET /admin/strict-manifest gives the manifest keeping everything held
now, the values for review and the heads they are at;
continuum-signed strict shows them, signs once confirmed and proposes
at those heads.
The sim a_swarm_turning_strict_through_a_remake: a member plants a
registry entry as a manager; the operator's manifest leaves it out; a
and b, given the key, commit; c, without, follows; the entry is gone
and the policy strict everywhere.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The user's name, top right, opens a menu:
* "credentials…": the user's keys (GET /auth/keys), one added with its
.pub line or removed but the last (/auth/keys/{id}); under a strict
policy, how to sign them anew (continuum-signed key-add, key-remove);
and where its password is: each node's --htpasswd, its operator's.
* "log out": this browser's session ends. Where the page got in by
what it can't end (the browser's password, the proxy's), it is greyed
(aria-disabled, not disabled, so a click still lands), its tooltip
saying how to log out, and a click says so in a dialog.
The log out button and the text beside the name from 1165 go into it.
Checked in Chromium against stubbed endpoints: the menu, both log out
cases, the credentials dialog, no errors, nothing the content policy
refuses.
|
| |
|
|
|
|
|
|
| |
A user managing its own keys (1168) could remove the last, and then
neither sign nor log in by key. It can't now: DELETE /auth/keys/{id}
of the last answers 409, the rules take a user's change of its own keys
only with one left after it, and under a strict policy its own entry
signed only with one key at least. One who may manage users still may.
|
| |
|
|
|
|
|
|
|
| |
What is meant for browsers logs in on its page (/login), with the
passwords of --htpasswd; basic authentication's challenge stays for the
API, so curl -u keeps working. 1164 kept it from a page's own requests;
now nothing under /admin/dashboard is challenged either, whatever the
request: a browser opening the page without a user is sent to /login,
anything else answered 401 without a challenge.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A node told (REFUSALS) that it holds a change the swarm's rules refuse,
and whose own rules refuse it too, dropped it and everything built on
it on one peer's word, however much that was; its own writes among them
went to the quarantine, the others' nowhere.
* A drop taking more than 100 changes, or more than ten and a tenth of
the document, now
waits until a second member names the same change: the node's rules
and one peer's word could both be off. Logged as an error, counted in
/admin/stats (sync.dropsHeld).
* The others' changes dropped are listed with the refused (GET
/admin/refused, "taken unjudged, dropped"), for operators to see.
The cheap half of asking the others (future-work.md, Telling a
malicious change from a wrong policy).
|