test: tighten two checks in the persisted-field harness (closes #379)
The polarity check now counts only `hostile` and `falsy` values, not a `hostileRestore` value, which reaches only the views its entry names. The stale index 5 moves into `hostile` for `selectedWallet` and `selectedAddress`, as each field's one value still truthy after the floor. Each `hostileRestore` entry declares whether its boot lands on its view or falls back to Home, and the test checks the one view on screen. A value driven onto every restorable view lists only the views it falls back on, so a view added later is driven by default. The header and the README restate the remaining limits as built and say which boots are held to where the popup lands. Model: opus-5-5
This commit was merged in pull request #498.
This commit is contained in:
@@ -1263,14 +1263,21 @@ path rather than on the home screen. Read the claim narrowly, as that file
|
||||
states it: what those boots prove is no structural dereference on the code paths
|
||||
a WHOLLY-CORRUPTED PROFILE takes, which is not every path a stored record takes.
|
||||
Not driven: any pairing of values the four slots do not produce, a view only
|
||||
forward navigation opens, anything behind a click, and everything a healthy
|
||||
profile reaches. Within that boundary the verdict is unconditional — if one of
|
||||
those boots leaves the popup unhealthy or off the view it stored, `make check`
|
||||
fails, including when it takes two corrupted fields at once, because the verdict
|
||||
is the combined boot and the per-field re-boot that names a culprit can only
|
||||
decorate the message. So does a field that gains a floor while its row still
|
||||
claims it has none, and so does a field added to `PERSISTED_FIELDS` with no row
|
||||
at all. The per-field justification that used to live in the header of
|
||||
forward navigation opens, anything behind a click or a timer, and, of what a
|
||||
healthy profile reaches, anything beyond its boot onto each view the popup can
|
||||
reopen onto. The fields the router does not read share a slot on each boot, so
|
||||
one of them truthy while another is falsy is reached only where the falsy slot
|
||||
pairs a field that cannot be falsy with one that is. Within that boundary the
|
||||
verdict is unconditional — if one of those boots leaves the popup unhealthy,
|
||||
`make check` fails, including when it takes two corrupted fields at once,
|
||||
because the verdict is the combined boot and the per-field re-boot that names a
|
||||
culprit can only decorate the message. The combined boot must also land on the
|
||||
view it stored, and each value driven only onto the restore path must land on
|
||||
its view, or fall back to Home, as its row declares; a field the router reads
|
||||
can legitimately change which view renders, so its own sweep is held to health
|
||||
alone. `make check` also fails on a field that gains a floor while its row still
|
||||
claims it has none, and on a field added to `PERSISTED_FIELDS` with no row at
|
||||
all. The per-field justification that used to live in the header of
|
||||
`src/shared/stateSchema.js` shipped a false claim in three consecutive changes,
|
||||
each caught only by a reviewer re-deriving thirty fields by hand.
|
||||
|
||||
|
||||
@@ -44,6 +44,20 @@ then continue tagging as milestones land.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-07: Two holes in what `tests/persistedFieldContract.test.js` checks
|
||||
are closed ([#379](https://git.eeqj.de/sneak/AutistMask/issues/379)). The
|
||||
check that every swept field is driven both truthy and falsy counts only
|
||||
`hostile` and `falsy` values, which the sweep drives onto every restorable
|
||||
view, and no longer a `hostileRestore` value, which reaches only the views its
|
||||
entry names; the stale index 5 moves into `hostile` for `selectedWallet` and
|
||||
`selectedAddress`, as the one value of either still truthy after the floor.
|
||||
Each `hostileRestore` entry declares whether its boot lands on its view or
|
||||
falls back to Home, and is held to it. The limits that remain, fields that
|
||||
share a slot on one boot and anything no stored record reaches by itself, are
|
||||
restated as built in the file's header and the README, which now also say
|
||||
which boots are held to where the popup lands and that a healthy profile is
|
||||
booted onto every restorable view.
|
||||
|
||||
- 2026-10-07: Every password error in the popup is shown the same way
|
||||
([#493](https://git.eeqj.de/sneak/AutistMask/issues/493)): with `showError()`
|
||||
and `hideError()` in a fixed-height error line below the password field, as
|
||||
|
||||
@@ -49,22 +49,37 @@
|
||||
// every path a stored record takes, and the difference is the whole of what
|
||||
// this file does not cover:
|
||||
//
|
||||
// - Only the values in the table, in the SLOT arrangement below: four value
|
||||
// combinations per view, not the product of twelve fields. A dereference
|
||||
// reached only under a pairing no slot produces is not driven at all.
|
||||
// - Only what a stored record reaches by ITSELF. A view only forward
|
||||
// navigation opens, and anything behind a click, is not driven.
|
||||
// - Nothing about the paths a HEALTHY profile takes, which is most of the
|
||||
// popup. This file is a floor under one defect class, not a proof about
|
||||
// the renderers.
|
||||
// - Only the values in the table, in the SLOT arrangement below. On the
|
||||
// restore path the twelve fields the router does not read are corrupted
|
||||
// together, every field on the same slot, so a view gets four value
|
||||
// combinations of them, not their product. A branch entered only when one
|
||||
// of them is truthy and another falsy is reached only where the falsy
|
||||
// slot happens to pair a field that cannot be falsy with one that is.
|
||||
// Each field the router reads is corrupted alone, over an otherwise
|
||||
// well-formed record.
|
||||
// - Only what a stored record reaches by ITSELF, as the boot renders it. A
|
||||
// view only forward navigation opens, anything behind a click, and
|
||||
// anything behind a timer (bootPopup() records every interval, and this
|
||||
// file never runs one) is not driven.
|
||||
// - Of the paths a HEALTHY profile takes, only its boot onto each
|
||||
// restorable view ("the base profile the sweep corrupts" below). The rest
|
||||
// of the popup is not covered: this file is a floor under one defect
|
||||
// class, not a proof about the renderers.
|
||||
//
|
||||
// Within that boundary it is unconditional: if one of these boots leaves the
|
||||
// popup unhealthy or off the view it stored, this file goes red — including
|
||||
// when it takes two corrupted fields at once, because the verdict is the
|
||||
// combined boot itself and the per-field re-boot below can only decorate the
|
||||
// message. That last part is the one thing an earlier version got wrong: it
|
||||
// asserted on the per-field list, so an observed dead popup that no single
|
||||
// field reproduced was reported green.
|
||||
// Within that boundary it is unconditional: if a boot that corrupts a field
|
||||
// leaves the popup unhealthy, this file goes red — including when it takes
|
||||
// two corrupted fields at once, because the verdict is the combined boot
|
||||
// itself and the per-field re-boot below can only decorate the message. That
|
||||
// last part is the one thing an earlier version got wrong: it asserted on the
|
||||
// per-field list, so an observed dead popup that no single field reproduced
|
||||
// was reported green.
|
||||
//
|
||||
// Where the popup lands is held for some of those boots and not others. The
|
||||
// combined boot must land on the view it stored, and each `hostileRestore`
|
||||
// value must land on its view, or fall back to Home, as its entry declares.
|
||||
// The boots in "a hostile routing value restoring onto" are held to health
|
||||
// alone, because a value in a field the router reads legitimately changes
|
||||
// which view renders; so are the boots onto Home, which store no view.
|
||||
//
|
||||
// Booting every field separately at every value would be several hundred boots
|
||||
// and most of the suite's budget; this is forty-four. Widening it further is
|
||||
@@ -128,7 +143,11 @@ const sweptValues = (row) => [...row.hostile, ...(row.falsy || [])];
|
||||
// list short and pointed. `floorOnly` is extra values checked against the
|
||||
// floor alone, which is pure and free. `hostileRestore` is extra values driven
|
||||
// through the restore path only, for a value that means nothing until a
|
||||
// particular branch's gate has let it past.
|
||||
// particular branch's gate has let it past. Each of its entries names the
|
||||
// `views` it is driven onto and declares whether the boot lands on them
|
||||
// (`restored: true`) or falls back to Home (`restored: false`), so a value
|
||||
// written for one renderer cannot stop reaching it unnoticed. A value driven
|
||||
// onto every restorable view is written with everyRestorableView() below.
|
||||
//
|
||||
// `falsy` is the other POLARITY of a swept field, driven for the same reason.
|
||||
// It is not a value src/ never writes — for three of these fields it is the
|
||||
@@ -139,6 +158,23 @@ const sweptValues = (row) => [...row.hostile, ...(row.falsy || [])];
|
||||
// falsy value stored under that field comes back TRUTHY from the floor, so no
|
||||
// `!state.x` branch is reachable from a stored record at all.
|
||||
|
||||
// The `hostileRestore` entries for a value driven onto every restorable view:
|
||||
// it falls back to Home on the views listed in `fallsBackOn` and lands on every
|
||||
// other one, so a view added to RESTORABLE_VIEWS is driven, and expected to
|
||||
// land, without editing the row.
|
||||
function everyRestorableView(value, fallsBackOn) {
|
||||
return [
|
||||
{ value, views: fallsBackOn, restored: false },
|
||||
{
|
||||
value,
|
||||
views: [...RESTORABLE_VIEWS].filter(
|
||||
(view) => !fallsBackOn.includes(view),
|
||||
),
|
||||
restored: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const CONTRACT = [
|
||||
{
|
||||
field: "wallets",
|
||||
@@ -280,28 +316,38 @@ const CONTRACT = [
|
||||
kind: KIND.SCALAR,
|
||||
// The prototype members are the whole point: `wallets["map"]` is
|
||||
// TRUTHY, so hasValidAddress()'s `&&` does not short-circuit and
|
||||
// `.addresses[…]` throws. A stale INTEGER is the safe case.
|
||||
hostile: ["map", "__proto__", { a: 1 }],
|
||||
// `.addresses[…]` throws. A stale INTEGER is the safe case and has to
|
||||
// stay so. 5 is one, out of range for the one wallet in the profile,
|
||||
// and it is also this field's truthy polarity: every other value here
|
||||
// comes back from the floor as null.
|
||||
hostile: ["map", "__proto__", { a: 1 }, 5],
|
||||
floorOnly: ["length", "constructor", "toString", "0", -1, 1.5, true],
|
||||
holds: isIndexOrNull,
|
||||
// SCALAR, and swept anyway: the restore path is precisely why this
|
||||
// field gained a floor, so the sweep is the regression guard on it.
|
||||
alsoSweep: true,
|
||||
routes: true,
|
||||
// A stale INTEGER index, which reaches the restore path by a different
|
||||
// route from the prototype members above — falsy or out of range
|
||||
// rather than truthy — and has to keep being the safe case.
|
||||
hostileRestore: [{ value: "length" }, { value: 5 }],
|
||||
// Comes back from the floor as null, which hasValidAddress() reads as
|
||||
// nothing selected: the popup falls back to Home on the five views
|
||||
// that need an address, and lands on every other one.
|
||||
hostileRestore: everyRestorableView("length", [
|
||||
"address",
|
||||
"address-token",
|
||||
"receive",
|
||||
"transaction",
|
||||
"confirm-tx",
|
||||
]),
|
||||
},
|
||||
{
|
||||
field: "selectedAddress",
|
||||
kind: KIND.SCALAR,
|
||||
hostile: ["map", "__proto__", { a: 1 }],
|
||||
// 5 for the same reason as in selectedWallet: a stale index, and the
|
||||
// one value here still truthy after the floor.
|
||||
hostile: ["map", "__proto__", { a: 1 }, 5],
|
||||
floorOnly: ["length", "constructor", "toString", "0", -1, 1.5, true],
|
||||
holds: isIndexOrNull,
|
||||
alsoSweep: true,
|
||||
routes: true,
|
||||
hostileRestore: [{ value: 5 }],
|
||||
},
|
||||
{
|
||||
field: "currentView",
|
||||
@@ -325,7 +371,9 @@ const CONTRACT = [
|
||||
// container shapes below onto every restorable view; hostileRestore
|
||||
// adds the records that PASS a branch's gate and then hand its
|
||||
// renderer something it dereferences, which is where the entries are
|
||||
// actually decided.
|
||||
// actually decided. The guard refuses each single-view record below,
|
||||
// so each is declared to fall back to Home: one that started landing
|
||||
// would be reaching the renderer it was written against.
|
||||
hostile: [42, "notarecord", { a: 1 }, [1, 2]],
|
||||
// `structuredClone(saved.viewData || {})`: the container is never falsy
|
||||
// in state whatever was stored, so no `!state.viewData` branch exists to
|
||||
@@ -334,11 +382,20 @@ const CONTRACT = [
|
||||
hostileRestore: [
|
||||
// success-tx passes on `data.hash`, and renderSuccess() then calls
|
||||
// toAddressHtml(d.to) -> addressTitle() -> address.toLowerCase().
|
||||
{ value: { hash: "0x1" }, views: ["success-tx"] },
|
||||
{ value: { hash: "0x1", to: 42 }, views: ["success-tx"] },
|
||||
{
|
||||
value: { hash: "0x1" },
|
||||
views: ["success-tx"],
|
||||
restored: false,
|
||||
},
|
||||
{
|
||||
value: { hash: "0x1", to: 42 },
|
||||
views: ["success-tx"],
|
||||
restored: false,
|
||||
},
|
||||
{
|
||||
value: { hash: "0x1", to: ADDRESS, decoded: { details: 7 } },
|
||||
views: ["success-tx"],
|
||||
restored: false,
|
||||
},
|
||||
{
|
||||
value: {
|
||||
@@ -347,12 +404,25 @@ const CONTRACT = [
|
||||
decoded: { details: [{ address: 42 }] },
|
||||
},
|
||||
views: ["success-tx"],
|
||||
restored: false,
|
||||
},
|
||||
// error-tx passes on `data.message`, same dereference.
|
||||
{ value: { message: "boom" }, views: ["error-tx"] },
|
||||
{ value: { message: "boom", to: 42 }, views: ["error-tx"] },
|
||||
{
|
||||
value: { message: "boom" },
|
||||
views: ["error-tx"],
|
||||
restored: false,
|
||||
},
|
||||
{
|
||||
value: { message: "boom", to: 42 },
|
||||
views: ["error-tx"],
|
||||
restored: false,
|
||||
},
|
||||
// transaction passes on `data.tx`.
|
||||
{ value: { tx: { hash: "0x1" } }, views: ["transaction"] },
|
||||
{
|
||||
value: { tx: { hash: "0x1" } },
|
||||
views: ["transaction"],
|
||||
restored: false,
|
||||
},
|
||||
{
|
||||
value: {
|
||||
tx: {
|
||||
@@ -363,9 +433,14 @@ const CONTRACT = [
|
||||
},
|
||||
},
|
||||
views: ["transaction"],
|
||||
restored: false,
|
||||
},
|
||||
// confirm-tx passes on `data.pendingTx`.
|
||||
{ value: { pendingTx: { amount: "1" } }, views: ["confirm-tx"] },
|
||||
{
|
||||
value: { pendingTx: { amount: "1" } },
|
||||
views: ["confirm-tx"],
|
||||
restored: false,
|
||||
},
|
||||
{
|
||||
value: {
|
||||
pendingTx: {
|
||||
@@ -376,6 +451,7 @@ const CONTRACT = [
|
||||
},
|
||||
},
|
||||
views: ["confirm-tx"],
|
||||
restored: false,
|
||||
},
|
||||
// wait-tx passes on `pendingWait.hash`; restoreWait() has checked
|
||||
// the fields below it since it was written, and this is the
|
||||
@@ -388,20 +464,29 @@ const CONTRACT = [
|
||||
},
|
||||
},
|
||||
views: ["wait-tx"],
|
||||
restored: false,
|
||||
},
|
||||
// A record that passes EVERY branch's gate at once, driven onto
|
||||
// every restorable view: a branch a view does not read must stay
|
||||
// one it does not read, and each renderer must survive the fields
|
||||
// another branch left behind.
|
||||
{
|
||||
value: {
|
||||
// one it does not read. The five views with a viewData branch
|
||||
// refuse it; every other one renders it, and must survive the
|
||||
// fields every branch left behind.
|
||||
...everyRestorableView(
|
||||
{
|
||||
hash: "0x1",
|
||||
message: "boom",
|
||||
tx: { hash: "0x1" },
|
||||
pendingTx: { amount: "1" },
|
||||
pendingWait: { hash: "0x1" },
|
||||
},
|
||||
},
|
||||
[
|
||||
"confirm-tx",
|
||||
"transaction",
|
||||
"wait-tx",
|
||||
"success-tx",
|
||||
"error-tx",
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -583,6 +668,11 @@ const HEALTHY = { errors: [], blank: false };
|
||||
// set is all-truthy by construction, so without a falsy slot a dereference
|
||||
// behind `if (!state.x)` is never reached on the boot that corrupts x — the
|
||||
// same falsy-collapse blind spot the fields below were floored for.
|
||||
//
|
||||
// Only `hostile` and `falsy` values count. Those go through the sweep below,
|
||||
// which covers every restorable view; a `hostileRestore` entry is driven onto
|
||||
// only the views it names, so a polarity it alone supplied might reach a single
|
||||
// renderer.
|
||||
describe("both polarities of every swept field are driven", () => {
|
||||
const FALSY_STORED = [0, "", false, null];
|
||||
const floored = (field, value) =>
|
||||
@@ -606,10 +696,9 @@ describe("both polarities of every swept field are driven", () => {
|
||||
test(`${row.field}: truthy and falsy`, () => {
|
||||
// What the boots below actually drive, floored the way a renderer
|
||||
// sees it — not what the row says it drives.
|
||||
const driven = [
|
||||
...sweptValues(row),
|
||||
...(row.hostileRestore || []).map((entry) => entry.value),
|
||||
].map((value) => floored(row.field, value));
|
||||
const driven = sweptValues(row).map((value) =>
|
||||
floored(row.field, value),
|
||||
);
|
||||
|
||||
expect({
|
||||
truthy: driven.some((value) => Boolean(value)),
|
||||
@@ -865,20 +954,27 @@ describe("every field the router does not read, corrupted at once, onto", () =>
|
||||
|
||||
// The values that only mean something on the restore path: a viewData that
|
||||
// PASSES a branch's gate and then hands its renderer something dereferenced,
|
||||
// and the index values whose route through hasValidAddress() differs from the
|
||||
// row's own hostile set.
|
||||
// and a selectedWallet the floor turns into nothing selected. Each boot must
|
||||
// throw nothing and show exactly the view its entry says it lands on: its own,
|
||||
// or Home, so a value written for one renderer cannot stop reaching it and
|
||||
// still pass.
|
||||
describe("a restore-only hostile value onto", () => {
|
||||
for (const row of CONTRACT) {
|
||||
for (const entry of row.hostileRestore || []) {
|
||||
for (const view of entry.views || RESTORABLE_VIEWS) {
|
||||
for (const view of entry.views) {
|
||||
test(`${view}: ${row.field} = ${JSON.stringify(
|
||||
entry.value,
|
||||
)}`, async () => {
|
||||
await expect(
|
||||
bootHealth(
|
||||
restoringOnto(view, { [row.field]: entry.value }),
|
||||
),
|
||||
).resolves.toEqual(HEALTHY);
|
||||
const env = await bootPopup(
|
||||
restoringOnto(view, { [row.field]: entry.value }),
|
||||
);
|
||||
expect({
|
||||
errors: env.pageErrors,
|
||||
visible: env.visibleViews(),
|
||||
}).toEqual({
|
||||
errors: [],
|
||||
visible: [entry.restored ? view : "main"],
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user