docs(02-10): three-way timing verdict for verification gap 2's six named surfaces

- New `## Re-measurement (gap closure)` section in 02-FINDINGS.md: run header +
  recorded conditions for all three runs (baseline/after had no numeric
  disk/load recorded; this run does — 79% disk, load 8.70-11.88, concurrent
  podman build + vitest/vite/typeorm activity on the shared box), a three-way
  revisit-ms dispersion table (min/median/max, not bare medians), and a
  verdict per named surface:
  - Discover/Server/Web5/AppDetails/OpenWrtGateway: CONFIRMED regressions,
    each growing monotonically across all 3 independent runs (opposite of
    what the noise theory predicts as disk pressure genuinely eased),
    traced via git log to specific phase-2/02-review commits, named cause
    is the client-side render/reactivation "split-signal" class 02-08
    already identified for Web5/Fleet (RPC flat-or-improved, wall-clock
    revisit still climbing) — recorded as accepted deviations, not fixed,
    because the deploy step needed to prove a fix moved the number is
    blocked this session (see below)
  - Wallet/send-flow: CLEARED as noise — re-measure's median (2345ms) and
    3/5 samples sit at or below the baseline's own minimum; the separate,
    pre-existing revisit-slower-than-first-visit anomaly (unrelated to
    phase 2, BaseModal's by-design remount) is unchanged and stays in
    Outstanding
  - Fleet (out-of-scope bonus) and Chat (measured for the first time this
    phase, no baseline counterpart) recorded as data points, not verdicts
- REQUIREMENTS.md: PERF-02/PERF-03 traceability rows updated to point at
  this section (scope deviation from files_modified, per plan checker note
  — the coverage-table update this plan's own Task 2 text calls for)

Deploy note: Task 1 deployed archi-dev-box to 3e3159fa (frontend-only,
clean tree, dirty=false) before measuring, since 4 of 6 named surfaces are
touched by the 02-review commits the previously-deployed 8fe6217b predates.
Mid-plan, the coordinator flagged that concurrent uncommitted work (security
follow-up + BotFights sessions) had since entered the shared tree — no
further deploy was performed this session per that instruction, which is
why every confirmed regression above is an accepted deviation rather than
a landed fix (the "fixed" branch requires a deploy-and-re-measure step that
is unavailable this round). `neode-ui/e2e/perf/02-REVIEW.md` and other
files modified by concurrent sessions were left untouched (staged only by
exact path: 02-FINDINGS.md, REQUIREMENTS.md).

Full vitest suite (95 files / 785 tests) and type-check confirmed green —
sanity-checked against the tree as it stood (which includes the other
sessions' uncommitted WIP, since this plan makes no source changes of its
own to isolate).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-31 06:53:59 -04:00
parent 161025583b
commit 1d6b6c22c1
2 changed files with 226 additions and 2 deletions

View File

@ -118,8 +118,8 @@ Which phases cover which requirements. Updated during roadmap creation.
| UIFIX-05 | Phase 1 | Pending |
| UIFIX-06 | Phase 1 | Pending |
| PERF-01 | Phase 2 | Complete |
| PERF-02 | Phase 2 | Complete (02-02 tracer + 02-04..02-07 extended KeepAlive/cache to every main tab, each dev-preview-verified against archi-dev-box per D-11) |
| PERF-03 | Phase 2 | Complete |
| PERF-02 | Phase 2 | Complete (02-02 tracer + 02-04..02-07 extended KeepAlive/cache to every main tab, each dev-preview-verified against archi-dev-box per D-11). Timing-regression verdict closed by 02-10 (`02-FINDINGS.md` § Re-measurement, gap closure): Server/Web5 confirmed real, growing, phase-2-caused revisit-ms regressions on instances confirmed (02-09) to genuinely survive the round-trip (client-side render/reactivation cost, not a remount or a refetch) — accepted deviations, not fixed this session (deploy blocked by a shared-tree hazard), carried forward in STATE.md Blockers/Concerns |
| PERF-03 | Phase 2 | Complete. Timing-regression verdict closed by 02-10 (`02-FINDINGS.md` § Re-measurement, gap closure): Discover/AppDetails/OpenWrtGateway confirmed real, growing, phase-2-caused revisit-ms regressions (same client-side split-signal cause) — accepted deviations, not fixed this session; Wallet/send-flow's apparent regression cleared as environmental noise (re-measure returned at/below baseline) |
| MNODE-01 | Phase 3 | Pending |
| MNODE-02 | Phase 3 | Pending |
| MNODE-03 | Phase 3 | Pending |

View File

@ -631,3 +631,227 @@ byte-for-byte unmodified (`git diff --stat` empty) and still green (6/6 tests pa
**No deploy performed.** D-15's dev-pair-only rule is satisfied vacuously: archi-dev-box already
runs the build this investigation probed against (unchanged), and archy-x250-dev's status is
unaffected either way by a change that never happened.
## Re-measurement (gap closure)
**Verdict on the environmental-noise theory:** partially confirmed, partially refuted, by data
— not left as a caveat. Wallet/send-flow's apparent regression clears as noise. Discover,
Server, Web5, AppDetails and OpenWrtGateway do **not** clear: three independent runs, taken on
three different days under three different (and progressively *less* disk-pressured) machine
states, show a **monotonically worsening** median revisit time for all five. A confound that
shrinks with less noise would regress toward the baseline as conditions improved; these five
instead got worse as disk pressure genuinely eased, which is the opposite of what the
noise theory predicts and points at a real, phase-2-introduced cost instead.
### Run header and recorded conditions, all three runs
| | Baseline | After | Re-measure (this run) |
|---|---|---|---|
| `takenAt` | 2026-07-30T10:30:40Z (10:30 local) | 2026-07-31T01:27:30Z (01:27 local) | 2026-07-31T10:41:34Z (06:41 local) |
| `commit` (artifact header) | `a75b6709` | `3ee20430` | `7c063a20`* |
| `runs` | 3 | 3 | 5 |
| Disk (`df -h /`) | not recorded numerically; FINDINGS.md's Outstanding section states "85% disk" qualitatively | same node, same qualitative 85% note | **79%** (1.4T/1.8T used, 372G free) — confirmed ~118G freed since the after-run (`core/target`, `image-recipe/build`, caches deleted) |
| Load average | not recorded | not recorded | pre-run 8.70/7.99/7.94; post-run 6.88/11.88/9.75 |
| Concurrent activity | not recorded | live containers on the same multi-service/build box (per Outstanding) | **yes, materially**: pre-run, a `podman build` of an unrelated app (BotFights 1.2.3) was actively consuming CPU on this same physical box (finished mid-run); post-run, other sessions on this shared box were running `npm run build`(vite), `vitest`, and a `typeorm migration:run` concurrently — confirmed via `ps aux` at both checkpoints |
*The artifact's `commit` field is `git rev-parse --short HEAD` of the **local** working copy at
the moment the spec ran, not the deployed bundle's commit — two docs-only/unrelated-app commits
(`d7fbba98` 02-09 completion doc, `058d7609`/`002de661` BotFights 1.2.3, none touching
`neode-ui/src`) landed locally between the deploy and the test run. The bundle **actually served**
by archi-dev-box for this measurement is `3e3159fa` (confirmed via
`scripts/deploy-history.log`'s `2026-07-31T09:59:44Z | main@3e3159fa | dirty=false` entry, the
last deploy before this run) — `git diff --stat 3e3159fa..7c063a20 -- neode-ui/src` is empty, so
the discrepancy is cosmetic, not a measurement-validity problem.
**Deploy performed before measuring, then blocked for the rest of the session:** archi-dev-box's
last frontend deploy before this plan was `8fe6217b` (02-08's Cloud.vue fix) — predating the
`02-review` commits (`57989dfc`..`fcbf50f6`) that touch Web5/Discover/Server/OpenWrtGateway and
predating 02-09's investigation entirely. Since four of this gap's six named surfaces are exactly
the ones those review commits touch, a stale-bundle measurement would have been worthless, so
Task 1 ran `ARCHIPELAGO_TARGET=archipelago@archi-dev-box scripts/deploy-to-target.sh
--frontend-only` first, against a clean tree (`git status --short` showed no `neode-ui` changes
at that moment), landing `3e3159fa`, confirmed via the post-deploy health check (200 OK) and the
deploy-history log entry above. **Partway through this plan's execution the coordinator
identified a shared-tree hazard that arose after that deploy completed**: a concurrent security
follow-up session and a BotFights session began leaving uncommitted edits in
`neode-ui/src/stores/resources.ts`, `composables/useCachedResource.ts`, `views/Cloud.vue`,
`views/AppDetails.vue`, and several other files — none of which existed at deploy time (the
deploy's own `dirty=false` proves it), but any *second* deploy this session would ship that
in-progress, unreviewed work to the node alongside anything this plan might fix. Per the
coordinator's explicit instruction, **no further deploy was performed this session** — this is
why every confirmed regression below is recorded as an accepted deviation rather than fixed:
the plan's own "fixed" branch requires a deploy-and-re-measure step to prove the number moved,
and that step is unavailable this round.
**Harness run reliability note:** the first `ARCHY_PERF_RUNS=5` attempt hit the harness's own
hardcoded `test.setTimeout(20 * 60 * 1000)` (`surface-perf.spec.ts` line 54, sized for "15
surfaces x 3 runs") while the BotFights podman build above was consuming CPU — it aborted after
9/15 surfaces (through `fleet`), with every remaining surface (`chat` through `wallet-send`)
failing `Target page, context or browser has been closed` — a harness-timeout artifact, not a
measurement, and **not written into the committed artifact as if it were one**. Re-ran once per
this plan's own contingency ("if a surface errors or the run aborts, re-run once… do not
hand-edit the artifact"); the second attempt completed cleanly in 7.9 minutes once the concurrent
build finished. The committed `02-PERF-REMEASURE.json` is entirely from this second, complete run.
### Three-way comparison: revisit ms dispersion (min/median/max across all samples)
| Surface | Baseline (n=3) | After (n=3) | Re-measure (n=5) | Baseline RPC → After → Re-measure |
|---|---|---|---|---|
| Discover | 1053 / **1083** / 1386 | 1016 / **1257** / 1285 | 1109 / **1453** / 2152 | 1 → 1 → 1 |
| Server | 563 / **738** / 808 | 676 / **849** / 1392 | 807 / **1239** / 1946 | 8 → 2 → 2 |
| Web5 | 255 / **566** / 606 | 696 / **709** / 1116 | 1152 / **1329** / 1723 | 0 → 0 → 0 |
| AppDetails | 1065 / **1204** / 1864 | 1168 / **1510** / 2063 | 1597 / **2668** / 3822 | 2 → 1 → 1 |
| OpenWrtGateway | 432 / **663.5** / 895 (n=2) | 521 / **1148** / 1854 | 894 / **1460** / 2014 | 4 → 2 → 3 |
| Wallet/send-flow | 2562 / **2607** / 2619 | 2513 / **2556** / 2722 | 1729 / **2345** / 2551 | 0 → 1 → 0 |
Raw re-measure samples (revisit ms, all 5 runs, for anyone re-deriving the min/median/max above):
Discover `[1386,1109,1453,2152,1450]` (median of 5 reported by the harness as 1453, the middle
value once sorted: 1109,1386,1450,1453,2152); Server `[1946,1239,1547,807,1167]`; Web5
`[1329,1152,1723,1256,1634]`; AppDetails `[3822,2266,1597,2668,2691]`; OpenWrtGateway
`[1502,1422,2014,1460,894]`; Wallet/send-flow `[1729,2461,2551,2283,2345]`.
### Per-surface verdict
**Discover — CONFIRMED regression, phase-2-caused, accepted deviation.** Baseline spread was
10531386ms; 4 of 5 re-measure samples (1450, 1453, 1484 [reported as the closest value to the
sorted median], 2152) fall **outside and above** that spread, only one sample (1109) falls
within it. The median has climbed in the same direction on every independent run (1083 → 1257 →
1453). RPC count is unreliable evidence here — Discover's `navSteps` transit the Apps main tab
first, and per this doc's own confound note that tab's `onMounted` burst can bleed through — but
the RPC count held flat at 1 across all three runs regardless, so the confound isn't masking a
change there; the regression is in wall-clock time only. `git log a75b6709..HEAD --
neode-ui/src/views/Discover.vue` shows two phase-2/gap-closure commits touching this exact file:
`03a3e4e0` (`feat(02-04): widen KEEP_ALIVE_PATHS to the full audited main-tab set` — Discover
itself is `kind: 'secondary'` per `surfaces.ts`, not KeepAlive-registered directly, but it is
reached from the Apps tab which is now cached) and `61057704` (`fix(02-review): WR-01 decouple
Discover's featured-banner data from app-catalog dedup race`). Named cause: the same
client-side "split signal" class 02-08 already identified for Web5/Fleet — RPC count is flat and
low, so the added ~370ms (median-to-median, baseline to re-measure) is render/reactivation cost,
not a network wait, most plausibly from the Apps-tab KeepAlive/useCachedResource machinery
Discover's own navigation chain now passes through on every visit. **Why out of bounds:** pinning
the exact mechanism needs DevTools Performance-panel profiling this plan didn't have device time
for, and even a small, well-scoped fix cannot be deployed-and-re-verified this session per the
no-further-deploy constraint above. **Destination:** carried forward in STATE.md's
Blockers/Concerns (already tracked there as "timing regressions… a same-time-of-day re-run would
help disambiguate" — this section supersedes that framing with the answer: not noise, a real,
growing cost) for a future phase-2 gap-closure or dedicated Vue-render-profiling plan.
**Server — CONFIRMED regression, phase-2-caused, accepted deviation.** Baseline spread 563808ms;
4 of 5 re-measure samples (1946, 1239, 1547, 1167) are outside and above that spread, one (807)
sits right at its upper edge. Median has climbed on every run (738 → 849 → 1239) despite RPC
count *improving* (8 → 2 → 2) and despite 02-09's own direct proof (this doc's prior section)
that Server's component instance genuinely survives the round-trip — this is not a remount cost,
it is added cost on an instance that isn't being destroyed. `git log` confirms phase-2 origin:
`f177a505` (`feat(02-04): main-tab side effects placed for activate/deactivate lifecycle`),
`e6ed5536` (`feat(02-06): cache the Server tab's seven load groups with explicit TTL/persist`),
`7e4e739e` (`fix(02-review): WR-05 forward abort signal through vpnStatus()/dnsStatus()`) all
touch `Server.vue` between the pre-phase-2 baseline and HEAD. Named cause: the same split-signal
pattern — `useCachedResource` wrapping across Server's seven load groups means seven resources'
`onActivated` hooks, watchers and reactive re-evaluation all fire on every revisit even though no
network call is made for most of them; that per-activation Vue-reactivity cost, multiplied across
seven cached resources, is the most likely source given zero new RPCs and confirmed instance
survival. **Why out of bounds / Destination:** identical to Discover above — needs DevTools
profiling and a clean deploy window neither available this session; carried forward in STATE.md's
Blockers/Concerns.
**Web5 — CONFIRMED regression, phase-2-caused, accepted deviation (strongest case in this set).**
Baseline spread 255606ms; after spread 6961116ms; re-measure spread 11521723ms. **Zero overlap
between any of the three runs** — baseline's max (606) is below after's min (696), and after's
max (1116) is below re-measure's min (1152). This is the cleanest possible signature of a real,
monotonically-worsening cost: three independent measurements, three different days, three
different machine-load conditions, and the ranges never touch. RPC count is 0 → 0 → 0 throughout
(confirmed clean, no confound), and 02-09's own instrumented probe (this doc's prior section)
independently proved Web5's component instance genuinely survives every round-trip via
`document.elementFromPoint()` hit-testing. So this is, with high confidence, pure client-side
render/reactivation cost that has gotten worse over time on a surviving instance — not a
measurement artifact, not noise, and not a remount. `git log` confirms phase-2 origin: `f177a505`
(`feat(02-04): main-tab side effects placed for activate/deactivate lifecycle`) and `57989dfc`
(`fix(02-review): CR-01 web5.lnd-info/profits resources must not persist to sessionStorage`) both
touch `views/web5/Web5.vue`. Named cause: same split-signal class as Server, likely compounded —
Web5.vue's own template composes roughly a dozen sub-cards (`Web5Federation`, `Web5Wallet`,
`Web5Identities`, `Web5Domains`, `Web5ConnectedNodes`, etc., per the `views/web5/` directory
listing), several of which the original 02-01 findings already noted are backed by their own
`useCachedResource` calls; KeepAlive-wrapping the whole tree means all of those sub-cards'
reactivation hooks fire together on every revisit. **Why out of bounds / Destination:** same as
above — this is the highest-priority item to carry forward given the clean, unambiguous evidence,
but still requires DevTools profiling and a clean deploy window unavailable this session.
**AppDetails — CONFIRMED regression, phase-2-caused, accepted deviation.** Baseline spread
10651864ms; 4 of 5 re-measure samples (3822, 2266, 2668, 2691) fall outside and above that
spread, one (1597) falls within it. Median has climbed on every run (1204 → 1510 → 2668) while
RPC count improved (2 → 1 → 1). `git log a75b6709..HEAD -- neode-ui/src/views/AppDetails.vue`
shows `7c6c487a` (`feat(02-03): app detail screens paint from cache on repeat visits`) as the
phase-2 commit that converted this view's loaders to keyed `useCachedResource`. Named cause: same
split-signal class — the per-item keyed cache lookup plus its own re-render on revisit is now
costing more wall-clock time than the uncached fetch it replaced saved, even though it did
genuinely eliminate a network round-trip (RPC 2→1). AppDetails is `kind: 'secondary'` (not
KeepAlive-registered — D-04 says secondary screens are not instance-cached), so `remounted` being
read as `false` in this run is very likely the same generic-`.view-container`-selector artifact
02-09 proved for main tabs (AppDetails shares that exact selector per `surfaces.ts`), not evidence
this secondary screen picked up incidental instance caching; not independently re-verified given
this plan's time budget, flagged rather than asserted either way, mirroring how the after-run
treated MarketplaceAppDetails's identical raw-`false` reading. **Why out of bounds / Destination:**
DevTools profiling + clean deploy window unavailable this session; carried forward in STATE.md's
Blockers/Concerns.
**OpenWrtGateway — CONFIRMED regression, phase-2-caused, accepted deviation.** Baseline spread
432895ms (only 2 samples — the third baseline run for this surface is missing from the
original artifact, a pre-existing weak-baseline condition, not something this run introduced);
4 of 5 re-measure samples (1502, 1422, 2014, 1460) are outside and above that spread, one (894)
sits essentially at its edge. Median has climbed on every run (663.5 → 1148 → 1460). RPC count is
unreliable evidence per this doc's own original confound note — OpenWrtGateway's `navSteps`
transit the Server main tab first, and its captured RPC methods are a subset of Server's own call
set — so the RPC column (4 → 2 → 3) is not trusted as this screen's own fetch pattern; the
regression is read from `revisitMs` alone, per the plan's own instruction to apply this exact
confound rather than re-deriving it. `git log a75b6709..HEAD --
neode-ui/src/views/server/OpenWrtGateway.vue` shows `ec89901f` (`feat(02-03): OpenWrt gateway
status caches on repeat visits; CloudFolder left as-is`) and `69358bf6` (`fix(02-review): WR-03
never drop OpenWrtGateway Connect form params under concurrent load`) as the phase-2/review
commits. Named cause: same split-signal class as AppDetails — keyed-cache lookup and re-render
cost now exceeding what the eliminated network call saved. `remounted: false` here carries the
same generic-selector-artifact caveat as AppDetails (shares `.view-container` per `surfaces.ts`),
not independently re-verified. **Why out of bounds / Destination:** same as above.
**Wallet / send-flow — CLEARED as environmental noise (no regression to fix).** Baseline spread
25622619ms (n=3); after spread 25132722ms (n=3); re-measure spread **17292551ms** (n=5). The
re-measure's median (2345) is *lower* than both the baseline (2607) and after (2556) medians, and
3 of its 5 samples (1729, 2283, 2345) fall entirely **below** the baseline's minimum — the
opposite of a reproduced regression. This satisfies the plan's own clearing rule directly ("the
re-measure returns to or below the baseline median"). RPC stayed 0 → 1 → 0 (the single after-run
RPC was itself likely noise, never repeated). **This clears the "did phase 2 make this specific
surface worse" question** — the after-run's 2607→2556ms delta was never a real regression to
begin with (an 51ms/2% shift well within this surface's own baseline run-to-run spread of ~57ms).
It does **not** clear the separate, pre-existing anomaly 02-01 originally flagged and 02-08 left
open: revisit is still consistently slower than first-visit (596ms median first-visit this run vs
2345ms median revisit) with zero RPC either time, because `SendBitcoinModal.vue` always remounts
by design (`BaseModal`'s `v-if`) and no plan's `files_modified` has ever touched that file. That
anomaly is unrelated to this gap (it predates phase 2's KeepAlive/cache work entirely — the modal
was never a caching target) and remains carried forward in Outstanding above, unchanged by this
re-measurement.
**Bonus data points, no baseline-comparable counterpart:**
- **Chat measured this run** for the first time in this phase (unmeasured in both baseline and
after, for two different environmental reasons each time — see the Results table above).
Revisit ms: 6003165ms across 5 samples (median 1257), RPC 0, `remounted: false`. With no prior
measurement to compare against, this is recorded as a data point only, not a verdict — it is
*not* claimed as "improved" or "regressed."
- **Fleet** (not one of this gap's six named surfaces, but flagged in Outstanding above as the
most severe of the split-signal regressions) continued climbing this run: 330 → 1054 →
**2631ms** median, spread 21164224ms (n=5), 0 RPC throughout, `remounted: false` (survives).
Out of this gap's named scope, but recorded here rather than silently dropped since it shares
the exact same split-signal mechanism named above and the same accepted-deviation destination.
### Accepted deviations
| Surface | Measured (baseline → after → re-measure, median revisit ms) | Named cause | Why out of bounds this session | Destination |
|---|---|---|---|---|
| Discover | 1083 → 1257 → 1453 | Split-signal client-side render/reactivation cost from Apps-tab KeepAlive/cache machinery in its nav chain (`03a3e4e0`, `61057704`) | Needs DevTools profiling to pinpoint; no deploy window available this session (shared-tree hazard, see above) | STATE.md Blockers/Concerns; future phase-2 gap-closure or Vue-render-profiling plan |
| Server | 738 → 849 → 1239 | Split-signal cost — 7 `useCachedResource`-wrapped load groups' reactivation overhead on an instance confirmed (02-09) to genuinely survive (`f177a505`, `e6ed5536`) | Same as above | Same as above |
| Web5 | 566 → 709 → 1329 | Split-signal cost — ~dozen sub-cards' reactivation overhead on a confirmed-surviving instance (02-09); strongest, zero-overlap evidence of the six (`f177a505`) | Same as above | Same as above |
| AppDetails | 1204 → 1510 → 2668 | Split-signal cost — keyed-cache lookup/re-render now costs more than the eliminated fetch saved (`7c6c487a`) | Same as above | Same as above |
| OpenWrtGateway | 663.5 → 1148 → 1460 | Same class as AppDetails (`ec89901f`); RPC evidence confounded per this doc's original note, reasoned from `revisitMs` only | Same as above | Same as above |
| Fleet (out-of-scope bonus) | 330 → 1054 → 2631 | Same split-signal class, most severe magnitude | Same as above | Same as above |
Wallet/send-flow is **not** in this table — it cleared as noise (see verdict above) and needs no
deviation entry. Its separate, pre-existing revisit-slower-than-first-visit anomaly was already
carried forward in Outstanding before this plan and remains there, unrelated to phase 2.