mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
docs(skills): both shipped balance forms are bounds now
This commit is contained in:
1 parent
4d14e9ca54
commit
ef7466e61e
1 file changed
+36
-33
@@ -106,34 +106,34 @@ climbed), state a bound on it rather than a prediction of it.
|
||||
**Prefer an upper bound to an equality.** This is the single most valuable
|
||||
sentence in this file.
|
||||
|
||||
`examples/folio/sanderling/predicates.ts` states one rule about one app both
|
||||
ways, so the two are worth reading side by side. Each line is the last line of
|
||||
its predicate, after the guards, at a step where exactly one submit sits in the
|
||||
window:
|
||||
Folio shipped one of these both ways and the equality lost, so the two are worth
|
||||
reading side by side. Each line is the last line of a predicate in
|
||||
`examples/folio/sanderling/predicates.ts`, after the guards, at a step where
|
||||
exactly one submit sits in the window:
|
||||
|
||||
```ts
|
||||
// sound, committedAmountExceedsOneSubmit: the violation is moving by MORE
|
||||
// than the one submit in this window could account for
|
||||
Math.abs(currAccountBalance - prevAccountBalance) > typedAmount
|
||||
// tempting, submitChangesBalanceByTypedAmount: it moved by exactly what I typed
|
||||
// what folio's total-balance property demanded, until 6e8e6d5
|
||||
Math.abs(currTotalBalance - prevTotalBalance) === typedAmount
|
||||
// what it demands now
|
||||
Math.abs(currTotalBalance - prevTotalBalance) <= typedAmount
|
||||
// and the same bound stated as the violation, over the account's own balance
|
||||
Math.abs(currAccountBalance - prevAccountBalance) > typedAmount
|
||||
```
|
||||
|
||||
Both catch the bug, because a double submit moves the balance by twice the typed
|
||||
amount. Only the second also convicts an app that behaved. A balance that has
|
||||
not moved is a commit still in flight (folio's `createTransaction` runs in a
|
||||
coroutine), a submit the app rejected, or a tap that never landed, and none of
|
||||
those is evidence of anything.
|
||||
All three catch the bug, because a double submit moves the balance by twice the
|
||||
typed amount and twice x exceeds x. Only the equality also convicts an app that
|
||||
behaved. A balance that has not moved is a commit still in flight (folio's
|
||||
`createTransaction` runs in a coroutine, and Home's total re-renders on the
|
||||
store's own schedule), a submit the app rejected, or a tap that never landed,
|
||||
and none of those is evidence of anything.
|
||||
|
||||
The asymmetry is the point. Moving by more than one submit's worth is not
|
||||
something a correct app can do, so the bound needs no case for any of the three.
|
||||
The equality needs a case for each, and every one you forget is a false
|
||||
conviction. Compare the two guard stacks and the price is exactly legible: the
|
||||
equality declines on `confirmedApplied` and on `acrossRelaunch`, and the bound
|
||||
carries neither, because a submit that may not have landed and a restart that
|
||||
may have eaten the commit both leave the balance under the bound anyway. Those
|
||||
are the two facts the runner cannot promise (see below), and needing to guard
|
||||
against both is a cost of the equality, not of the app.
|
||||
conviction. Two of those cases are facts the runner cannot promise you (see
|
||||
below): an action it could not confirm was applied, and an action it had to
|
||||
relaunch the app after. Both leave the balance under the bound and both break an
|
||||
equality, so a bound counts them and an equality has to decline on them.
|
||||
|
||||
You do give something up, so make the trade deliberately. A bound cannot see a
|
||||
balance that moved by *less* than the typed amount, and for a ledger that is a
|
||||
@@ -344,8 +344,10 @@ without throwing once.
|
||||
**Absence is unknown, never a default.** Extractors return null when the element
|
||||
is not there, and a property handed null declines. `0`, `""` and `[]` are the
|
||||
values that turn a property into one that fires on healthy runs: folio's
|
||||
balances once parsed as `0` on web, so the check became `|0 - 0| === typed` and
|
||||
was false at every healthy submit. An empty list has the same problem in the
|
||||
balances once parsed as `0` on web, so the check, an equality at the time,
|
||||
became `|0 - 0| === typed` and was false at every healthy submit. Under today's
|
||||
bound the same `0` reads as `|0 - 0| <= typed` and passes at every submit
|
||||
instead, which is the same defect wearing green. An empty list has the same problem in the
|
||||
other direction, and it is worse because it looks reasonable. Android renders
|
||||
Home's own node a frame or two before its list, so `findAll` over the cards
|
||||
comes back empty while the screen already claims to be Home. That is unknown,
|
||||
@@ -388,23 +390,24 @@ of its states is unsound:
|
||||
One rule covers the last two, and it is the rule that decides shape 2 for you.
|
||||
An action the runner cannot fully vouch for **still counts toward a bound on
|
||||
what the app could have done**, and it **never licenses attributing an effect to
|
||||
it**. So a bound counts it and an equality has to decline on it. That is why
|
||||
`committedAmountExceedsOneSubmit` needs no `confirmedApplied` guard and no
|
||||
`acrossRelaunch` guard while `submitChangesBalanceByTypedAmount` needs both: a
|
||||
property demanding the effect of an action that may never have run, or that a
|
||||
restart may have swallowed, convicts the app of the runner's own uncertainty.
|
||||
it**. So a bound counts it and a property demanding an effect has to decline on
|
||||
it. That is why `committedAmountExceedsOneSubmit`, which only bounds how far the
|
||||
balance could have moved, needs no `confirmedApplied` guard and no
|
||||
`acrossRelaunch` guard, while `createdAccountHasNonZeroBalance`, which demands
|
||||
that a card appear, needs both. Demanding the effect of an action that may never
|
||||
have run, or that a restart may have swallowed, convicts the app of the runner's
|
||||
own uncertainty.
|
||||
|
||||
`relaunched` is the same shape of fact as `applied`, applied to app state rather
|
||||
than to dispatch. The action itself did happen. What nobody can promise across
|
||||
it is that the process ran continuously, that the commit survived, or that the
|
||||
screen is showing the same slice of the same list it was. So a property assuming
|
||||
continuous state declines, via `acrossRelaunch(lastAction)`, and folio uses it
|
||||
in three places: `createdAccountHasNonZeroBalance` declines because Home redraws
|
||||
from the top and the card carrying the typed name may be an older account laid
|
||||
out where the new one used to be, the equality property declines because it
|
||||
demands an effect, and `countSubmitsInWindow` uses it to **stop trusting its own
|
||||
refusal evidence**, since a relaunch is the one thing that can put a form state
|
||||
on screen other than the one the tap read.
|
||||
continuous state declines, via `acrossRelaunch(lastAction)`.
|
||||
`createdAccountHasNonZeroBalance` declines because Home redraws from the top and
|
||||
the card carrying the typed name may be an older account laid out where the new
|
||||
one used to be. `countSubmitsInWindow` uses the same call to **stop trusting its
|
||||
own refusal evidence**, since a relaunch is the one thing that can put a form
|
||||
state on screen other than the one the tap read.
|
||||
|
||||
Both fields are `true | null` rather than booleans, and that is deliberate: only
|
||||
the positive report is a fact the runner can vouch for, so `null` is "not
|
||||
|
||||
Reference in new issue
Block a user