| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The console appended messages as their streams brought them, a
subscribed node's later than this node's by its path, each line with
its node's own clock's time: lines of one moment came apart, and their
times read out of order.
Each message is now listed by when its node logged it, on this node's
clock, as the map runs them (`aheadOf`): one a slower stream brought
late goes in among those of its moment. The time shown is on this
node's clock; a subscribed node's own clock's time is in its tooltip.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Each node's message stream comes its own way to the page, a subscribed
node's over the network to it: messages logged 16 ms apart on three
nodes ran over a second apart, as their streams arrived.
Messages now run when they went, on this node's clock: each at the
time its node logged it, taken back by how far that node's clock is
ahead of this node's (the map's clock chain, `aheadOf`), a while
behind real time (`replayDelay`): the most any stream lagged in the
last 30 s and a tenth of a second more, shown after the sliders
(`behind: …`), each stream's lag behind the fastest in its tooltip.
The browser's clock is against all streams alike and drops out. A
message known only from its receiver (its sender not subscribed) is
placed at its arrival, starting a crossing before. Messages lagging
more than 10 s (a gap) don't run, nor hold the map behind.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The node sent its messages to the page every 2 s (`messages` events,
this node's and a subscribed node's), and this node's ran on the map
from its counts, which come with the state: the map showed a message 2
to 4 s after it went, the messages of each 2 s spread evenly over the
next.
The message log now tells those waiting of each message logged
(MessageLog::logged_after), and both streams send what was logged a
tenth of a second after the first of it (MESSAGES_GATHERED): a busy
node about ten events a second, a quiet one each message soon after it
went. The page runs messages as their streams bring them, this node's
from its own log too (with the addresses its PINGs went to), as far
apart as they were sent; more than six on a link and way at once are
shared out by kind over the moment, and after a gap only the last 2 s
run. The counts still give the rates and what was delivered.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
| |
api.md's Signed changes says what sent, all and none do, what each
costs, and which to choose: sent for nearly every swarm, all where each
change should carry its own proof, none to save signatures where only
protocol data and listed paths need them; and that signatures travel
with their changes, a message carrying none taking none. access.md no
longer says signatures come on their own within a second.
|
| |
|
|
|
|
|
|
|
|
| |
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: since 1184 a proposal is written as the user who asks, so
under a policy the node doesn't know it answers 409 like every user
write. remakes.md says so.
|
| |
|
|
|
|
|
|
|
| |
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, the replace side: a replace or import remake writes its
content as it is, at signed places too, so under a strict policy a
member majority can put plain values where envelopes were. access.md
says that remakes are outside the rule; keeping envelopes at signed
places in every kind is postponed to future-work.md.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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, N11: a value at _continuum/logouts/<name> that isn't a time
(read as damaged: every token revoked), or one far ahead, refuses every
token of the user, new ones too, until it is replaced. Any member can
write it (finding 10). The rules can't see the value (only that it is
written), so access.md says it, and how it is set right: the user logs
out again by password, or one who manages users revokes its tokens.
|
| |
|
|
|
|
|
|
|
| |
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.
|