21 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, gap_closure, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | gap_closure | requirements | user_setup | must_haves | |||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-ui-performance | 10 | execute | 7 |
|
|
true | true |
|
|
|
Purpose: PERF-02 and PERF-03 are both marked partial in 02-VERIFICATION.md solely because these rows have no verdict. A measurement with an untested confound is not evidence either way — this plan produces the third data point that turns "regressed or noisy, unresolved" into a per-surface answer. D-10's discipline is unchanged: name the cause from measurement before touching code.
Output: a third harness run (02-PERF-REMEASURE.json) taken with the instrument unmodified and the run conditions recorded, a three-way comparison that reports dispersion rather than bare medians, and a written verdict per named surface — cleared, fixed, or explicitly accepted with rationale.
Coverage: this plan covers verification gap 2 and both human-verification item 2 and the timing half of PERF-02/PERF-03. Gap 1 (Server's remount) is covered by 02-09-PLAN.md; the full multi-source coverage audit for this gap-closure set lives in 02-09-PLAN.md's <source_audit> section.
Constraints: the 02-01 harness (neode-ui/e2e/perf/{surfaces,measure,surface-perf.spec}.ts) is frozen — changing it would break comparability with both committed artifacts and invalidate the whole exercise. Deploy only to archi-dev-box, frontend-only, and only if a fix actually lands (D-15 — no fleet, no OTA). No new features. No visual or animation changes.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @CLAUDE.md@.planning/phases/02-ui-performance/02-CONTEXT.md @.planning/phases/02-ui-performance/02-VERIFICATION.md
Read only these parts of the large phase artifacts:
.planning/phases/02-ui-performance/02-FINDINGS.md—## Method(the exact run command, sample semantics and field meanings), the## Resultstable, and## Outstanding(the timing-variance caveat is the last-but-one bullet). Skip the## Addendum, which is about a different, already-closed issue..planning/phases/02-ui-performance/02-09-SUMMARY.md— what changed in the build this run measures.neode-ui/e2e/perf/surfaces.ts— surface ids,navStepsand the transit-confound notes foropenwrt-gateway,marketplaceandmarketplace-app-details.
Do not read 02-PERF-BASELINE.json, 02-PERF-AFTER.json or 02-PERF-REMEASURE.json into context — they are 50-60KB each and reading all three would consume most of this plan's budget for no benefit. Extract the fields you need with node -e or jq and work from the extracted table.
Before running, confirm the harness is unmodified since the after-run: git diff --stat 3ee20430 -- neode-ui/e2e/perf/ must be empty. If it is not, stop — a modified instrument makes the three artifacts incomparable and this entire plan pointless. (keepalive-remount-probe.spec.ts, added by 02-09, is a separate spec and does not count as a modification of the three harness files; confirm the diff is limited to that new file if anything shows.)
Record the run conditions BEFORE and AFTER the run, into the SUMMARY and into Task 2's findings section — these are the variables the noise theory turns on, and 02-08 recorded none of them: wall-clock local time at start and end, df -h / (disk usage — roughly 118G was freed since the after-run, so this number should differ materially from the 85% recorded then), uptime load averages, and whether any build, test run or container churn was active on the box during the window. The point is that this run's conditions are stated rather than inferred later.
Prefer a start time close to the baseline's 10:30 local window if the schedule allows — that is the controlled comparison 02-VERIFICATION.md's human-verification item 2 asks for. If the run must happen at a different hour, do not skip it and do not pretend the hour matches: record the actual time and compensate for the uncontrolled variable by sampling more, per the next paragraph.
Run with ARCHY_PERF_RUNS=5 rather than the default 3. This changes no harness code — it is the harness's own env knob — and the reported revisitMs/firstVisitMs stay medians, so they remain directly comparable to both prior artifacts. The extra samples exist so Task 2 can compute a real spread per surface instead of comparing three bare medians, which is the only way to separate a genuine regression from run-to-run variance.
Command shape (password supplied from the environment, never inline in a committed file or a transcript): cd neode-ui && ARCHY_BASE_URL=http://archi-dev-box ARCHY_PERF_RUNS=5 ARCHY_PERF_OUT=../.planning/phases/02-ui-performance/02-PERF-REMEASURE.json npx playwright test e2e/perf/surface-perf.spec.ts --project=chromium --reporter=line, with ARCHY_PASSWORD already exported.
Expect 15 rows. Mesh and Chat may again come back unmeasured — record whichever reason applies rather than presenting them as anything else, exactly as both prior runs did. Note one changed condition worth checking: the after-run's Chat block was the disk-usage toast from HealthNotifications.vue intercepting the close button, which was a symptom of the 85% disk; with the space freed, that block may simply be gone. If Chat measures this time, record it as a bonus data point and say plainly that it has no baseline-comparable counterpart.
If a surface errors or the run aborts, re-run once and keep both transcripts; do not hand-edit the artifact. Commit and push the artifact plus the recorded conditions (git add by path, Co-Authored-By trailer, git push gitea-ai main).
test -f .planning/phases/02-ui-performance/02-PERF-REMEASURE.json && node -e "const r=require('./.planning/phases/02-ui-performance/02-PERF-REMEASURE.json'); const rows=r.surfaces||r.results||r; console.log('rows:', Array.isArray(rows)?rows.length:Object.keys(rows).length); console.log('baseUrl:', r.baseUrl, 'runs:', r.runs, 'commit:', r.commit)"
git diff --stat 3ee20430 -- neode-ui/e2e/perf/surfaces.ts neode-ui/e2e/perf/measure.ts neode-ui/e2e/perf/surface-perf.spec.ts (must print nothing — the instrument is frozen)
02-PERF-REMEASURE.json exists with 15 rows, a baseUrl of http://archi-dev-box and runs: 5; the three harness files are provably unmodified since the after-run commit; the run's clock time, disk usage, load and concurrent-activity state are recorded; the artifact is committed and pushed.
Build the comparison table with a script, not by reading JSON into context. Extract, for every surface id present in all three artifacts, from each of 02-PERF-BASELINE.json, 02-PERF-AFTER.json and 02-PERF-REMEASURE.json: firstVisitMs, revisitMs, revisitRpcCount, remounted, and — this is the part 02-08 never used — the per-run values inside samples, reduced to min/median/max. The baseline and after artifacts already contain their own samples arrays (02-FINDINGS.md ## Method: "samples holds every individual run so no data is discarded"), so dispersion is computable for all three runs from what is already committed, with no re-run of history required. A one-off node -e script that prints a markdown table is the right tool; keep it in the transcript rather than committing a script file.
Then decide each of the six surfaces 02-VERIFICATION.md named — Discover, Server, Web5, AppDetails, OpenWrtGateway, wallet/send-flow — against this rule, and state which branch the data put it in:
- Cleared as environmental noise when the baseline, after and re-measure sample ranges overlap materially, or when the re-measure returns to or below the baseline median. Cite the actual ranges. "Overlapping spread" is a claim the numbers must show, not an assertion.
- Confirmed regression when the re-measure reproduces the after-run's elevated revisit time outside the baseline's sample spread. Then name the cause: identify the responsible mechanism the way 02-08 root-caused the Cloud connection-pool starvation — direct instrumentation on the device,
git logbisection against the pre-phase-2 baseline commita75b6709, and the surface's own RPC/remount fields — not by inspecting code and guessing. State plainly whether the cause is a phase-2 change or pre-existing.
Apply the confounds already documented rather than re-deriving them: OpenWrtGateway's and Discover's RPC counts bleed through from the tab their navSteps transit (Server and Apps respectively), so treat their RPC columns as unreliable and reason from revisitMs and remounted; wallet/send-flow is a modal that remounts by design via BaseModal's v-if, was never in any plan's files_modified, and its anomaly (revisit slower than first visit, zero RPC either way) is pure client-side cost — if it is confirmed as a real, still-present cost, name the mechanism.
Act on each verdict, in bounds:
- Cleared -> no code change. Write the verdict and the data.
- Confirmed AND caused by a phase-2 change AND fixable small -> fix it. "Small" means: confined to one file, covered by a vitest assertion that fails without it, and followed by
npm test+npm run type-check+npm run buildgreen, anARCHIPELAGO_TARGET=archipelago@archi-dev-box scripts/deploy-to-target.sh --frontend-onlydeploy (D-15, archi-dev-box only), and a targeted re-run of the harness for the affected surface(s) proving the number moved. No visual or animation change is permitted as part of any such fix. - Confirmed but caused by something pre-existing, or fixable only by a change that fails any of those bounds -> record an explicit accepted deviation. Write it for the verifier to act on: which surface, the measured numbers across all three runs, the named cause, why it is out of this phase's bounds, and where it belongs (a specific requirement such as UIFIX-06, or a named later phase). An accepted deviation with a named cause and a destination is a resolution; a shrug is not.
Write all of it into a ## Re-measurement (gap closure) section in 02-FINDINGS.md: the run header and recorded conditions from Task 1, the three-way table with dispersion, a verdict line per named surface, and a short ### Accepted deviations subsection listing anything not fixed. Keep the doc's citation discipline — every verdict points at the field that supports it. Keep the doc's candor: if the re-measure shows the after-run was mostly environmental and the architecture is fine, say so and show it; if it shows real regressions the phase shipped, say that just as plainly.
Finally, reconcile the record: if the verdicts change what PERF-02/PERF-03 can honestly claim, update the status note for those rows in .planning/REQUIREMENTS.md's coverage table to point at this section, so the next verification pass reads the resolved state rather than the old partial one. Commit and push (Co-Authored-By trailer, git push gitea-ai main).
grep -q "## Re-measurement (gap closure)" .planning/phases/02-ui-performance/02-FINDINGS.md && for s in Discover Server Web5 AppDetails OpenWrtGateway "send flow"; do grep -q "$s" .planning/phases/02-ui-performance/02-FINDINGS.md && echo "present: $s"; done
cd neode-ui && npm test 2>&1 | tail -10 && npm run type-check (required whether or not a fix landed — the suite must stay green)
02-FINDINGS.md has a ## Re-measurement (gap closure) section containing the recorded run conditions, a three-way table with per-run spread, and one verdict per named surface — each either cleared with the overlapping-range data that clears it, fixed with the change and its proof, or listed under ### Accepted deviations with a named cause and a destination. The vitest suite and type-check are green. Any fix that landed was deployed to archi-dev-box only and re-measured. Work is committed and pushed.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| operator workstation -> archi-dev-box UI login | The real node password crosses this boundary at runtime to drive the harness |
| harness -> committed measurement artifact | Observed traffic metadata is written into a repository file |
build artifact -> /opt/archipelago/web-ui on a real node |
Only if a confirmed-regression fix lands |
STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-02-10-01 | Information Disclosure | harness login (ARCHY_PASSWORD) |
high | mitigate | Password read only from the environment at runtime; never inlined into a command that gets pasted into a committed file, never written to 02-PERF-REMEASURE.json, the findings doc or the SUMMARY. The same handling 02-01 and 02-08 used. |
| T-02-10-02 | Information Disclosure | 02-PERF-REMEASURE.json contents |
medium | mitigate | The harness records RPC method names and timings only, never request or response bodies — its original threat mitigation, preserved because the harness is unmodified. Spot-check the artifact for anything resembling a token, credential or personal path before committing. |
| T-02-10-03 | Tampering | frozen harness files | medium | mitigate | Task 1 gates on git diff --stat over the three harness files being empty; a modified instrument silently invalidates every comparison in this plan, so it is checked rather than assumed. |
| T-02-10-04 | Tampering | scripts/deploy-to-target.sh --frontend-only (conditional) |
medium | mitigate | Only triggered by a confirmed, phase-2-caused, small fix; frontend-only, archi-dev-box only, no OTA and no fleet/catalog path (D-15); served bundle grepped post-deploy per CLAUDE.md's silent-no-op-build gotcha. |
| T-02-10-05 | Repudiation | measurement provenance | low | mitigate | The artifact header carries baseUrl, commit and runs, and Task 1 additionally records clock time, disk usage and load — so a later reader can tell which build and which machine state produced each number instead of trusting a verdict's summary of them. |
| T-02-10-SC | Tampering | npm/pip/cargo installs | high | accept | No package-manager install is planned — re-running an existing harness and comparing committed JSON needs no new dependency. If one becomes necessary, halt and route through the Package Legitimacy Gate before installing anything. |
| </threat_model> |
<success_criteria>
- Verification gap 2 is closed: no surface is left in the unexplained "regressed, cause unknown, confound untested" state that blocked the phase's clean pass.
- The noise-versus-regression question is answered with dispersion data from three runs, not asserted from three medians.
- Real regressions traceable to phase 2 and cheap to fix are fixed and re-measured; everything else is an explicit, actionable accepted deviation.
- The instrument stayed frozen, so all three artifacts remain a comparable series for any future run. </success_criteria>