mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-21 22:02:03 +00:00
chore: keep issue 824 investigation reports out of the PR
Remove the 21 mission reports from the tracked change set at the user's request. Keep their working copies and private evidence locally, and publish review findings, measurements and acceptance limitations through the pull request description and discussion instead of permanent repository documents. No production, test, fixture, build or runtime input is changed by this commit. The completed browser cycle remains red and the existing independent reviews keep their original source HEAD attribution; this documentation-only cleanup does not constitute acceptance or resolve issue 824.
This commit is contained in:
parent
f6a18d00bd
commit
acac9be29a
21 changed files with 0 additions and 3729 deletions
|
|
@ -1,121 +0,0 @@
|
|||
# Issue #824: native compaction renderer diagnosis
|
||||
|
||||
## Scope
|
||||
|
||||
This investigation covers the UI event path only. It does not exercise session
|
||||
pruning, the shared daemon, provider traffic, Electron/Linux, or the remote
|
||||
Windows backend. The fixture uses the real `SessionView`, native event
|
||||
dispatcher, OpenCode Solid reducer, CodeNomad message store, and virtualized
|
||||
transcript. Native client calls and `/api` traffic are isolated fakes.
|
||||
|
||||
The red reproduction is:
|
||||
|
||||
```powershell
|
||||
$env:TEMP = 'C:\Users\Admin\AppData\Local\Temp\opencode'
|
||||
$env:TMP = $env:TEMP
|
||||
node --import tsx --test --test-concurrency=1 packages/ui/tests/browser/compaction-responsiveness.test.ts
|
||||
```
|
||||
|
||||
## Confirmed behavior
|
||||
|
||||
A compaction for an inactive session still performs synchronous work in the
|
||||
single renderer that owns the active session:
|
||||
|
||||
1. `SSEManager.handleEvent()` calls `handleInstanceInvalidation()` for every
|
||||
native event.
|
||||
2. `handleInstanceInvalidation()` calls `applyOpenCodeDataEvent()` before it
|
||||
checks whether the session is active for visible projection.
|
||||
3. `applyOpenCodeDataEvent()` creates/retains a per-session OpenCode Solid data
|
||||
reducer and emits every `session.compaction.delta` into it.
|
||||
4. The bundled `@opencode/client` reducer finds the running compaction and
|
||||
appends every fragment to its `summary` in a Solid `produce()` update.
|
||||
5. CodeNomad delays only `projectOpenCodeMessages()` to 250 ms. That avoids
|
||||
repeatedly normalizing/hydrating the visible transcript, but it does not
|
||||
coalesce the upstream reducer work or callback scheduling.
|
||||
|
||||
The active transcript stayed byte-for-byte unchanged in the fixture. The
|
||||
timeline and virtualizer were therefore not the cause of the measured stall.
|
||||
No `message.list` request occurred during any burst. The one `session.active`
|
||||
call per sample comes from the synthetic terminal `compaction.failed` cleanup,
|
||||
not from delta delivery.
|
||||
|
||||
### October 1 isolated Chromium sample
|
||||
|
||||
Each fragment was 1 KiB. The fixture schedules a zero-delay active-session
|
||||
timer before dispatch and records browser Long Tasks. The final run reported:
|
||||
|
||||
| Delta events | Dispatch | Active timer delay | Long tasks |
|
||||
| ---: | ---: | ---: | ---: |
|
||||
| 4,096 | 14.1 ms | 14.2 ms | 0 |
|
||||
| 8,192 | 21.9 ms | 22.0 ms | 0 |
|
||||
| 16,384 | 42.6 ms | 42.8 ms | 0 |
|
||||
| 32,768 | 82.5 ms | 82.6 ms | 83 ms |
|
||||
|
||||
CDP sampling attributed named self time primarily to
|
||||
`applyOpenCodeDataEvent` (36.3 ms), OpenCode/Solid store access/update paths,
|
||||
and `projectMessages` (11.8 ms). A large `(program)` bucket remained
|
||||
unattributed by the sampling profiler. The near-linear count scaling points to
|
||||
per-event reducer/callback overhead rather than a whole-transcript DOM redraw
|
||||
or a superlinear summary concatenation in this corpus.
|
||||
|
||||
Two preceding runs also failed red at 32,768 fragments, with 80-85 ms timer
|
||||
delay and one 82-85 ms Long Task. A 16,384-fragment run varied around the Long
|
||||
Task boundary (approximately 42-70 ms), so the regression deliberately uses
|
||||
the larger corpus and asserts behavior, not a comparative performance gain.
|
||||
|
||||
## Interpretation and limits
|
||||
|
||||
This is a confirmed renderer scalability defect: inactive compaction content
|
||||
is reduced synchronously even though it cannot be displayed. It provides a
|
||||
mechanism for cross-session UI interference and demonstrates delayed active
|
||||
input/timers in the real UI stack.
|
||||
|
||||
It is not yet proof that normal EventSource delivery produces one callback
|
||||
containing 32,768 events. The current browser transport invokes `onBatch` with
|
||||
one event, while the reproduction deliberately presents a backlog directly to
|
||||
the dispatcher to make callback work deterministic. Real network task
|
||||
scheduling may interleave user input. The corpus is also 32 MiB of aggregate
|
||||
summary fragments, larger than many compactions. Therefore this result must
|
||||
not be presented as the sole cause of the reported multi-second freeze.
|
||||
|
||||
The test does not reproduce the daemon-side symptom where other native
|
||||
sessions stop progressing, nor the Linux Electron to Windows HTTP persistence
|
||||
after restart. Those require separate service/transport evidence. Native
|
||||
compaction and CodeNomad's pruning RPC are unrelated paths here.
|
||||
|
||||
Current V2 UI events are `session.compaction.started/delta/ended/failed` plus
|
||||
other granular `session.*` events. No current renderer handler for legacy
|
||||
`session.updated`, `message.updated`, or `message.part.updated` was found.
|
||||
|
||||
## Smallest recommended product patch
|
||||
|
||||
Coalesce `session.compaction.delta` **before** the OpenCode Solid reducer, keyed
|
||||
by instance and session, instead of only coalescing the later CodeNomad message
|
||||
projection:
|
||||
|
||||
- append fragments to a plain pending accumulator;
|
||||
- preserve an immediate revision fence on the first arrival so in-flight
|
||||
native message reads cannot publish stale content;
|
||||
- flush at most once per 250 ms to the reducer and visible projection;
|
||||
- cancel the pending aggregate on `ended` because that event carries the
|
||||
authoritative final text, and discard it on `failed`;
|
||||
- clear pending aggregates on instance disposal/reconnect;
|
||||
- do not materialize an inactive, unloaded transcript reducer solely for
|
||||
compaction deltas. A later activation should hydrate authoritatively.
|
||||
|
||||
The regression should retain this two-session browser fixture and add exact
|
||||
event-count instrumentation: thousands of inactive deltas must produce a
|
||||
bounded number of reducer emissions/projections, zero message-list reads, no
|
||||
active-transcript mutation, and no Long Task. Existing request-generation,
|
||||
revert, pruning, rotation, and terminal-authority tests must remain green.
|
||||
|
||||
## Files added for reproduction
|
||||
|
||||
- `packages/ui/tests/browser/compaction-responsiveness.test.ts`
|
||||
- `packages/ui/tests/browser/fixtures/compaction-responsiveness.tsx`
|
||||
|
||||
The initial timing-only test has since been replaced by deterministic reducer
|
||||
and projection counters in `compaction-responsiveness.test.ts`. It is red on
|
||||
the diagnosed implementation and green with the buffering correction. See
|
||||
`issue-824-compaction-ui-fix.md` for the current gates and their limits. No shared
|
||||
daemon, user profile, external network, or desktop restart is used.
|
||||
|
|
@ -1,484 +0,0 @@
|
|||
# Issue #824 — freeze investigation
|
||||
|
||||
Mission baseline: `366830f6` (`origin/dev`, 2026-10-01). Integration worktree:
|
||||
`D:\CodeNomad\.codenomad\worktrees\issue-824-performance`, branch
|
||||
`fix/issue-824-performance`. Confirmed corrections are integrated and independently
|
||||
reviewed, but final acceptance is not green. On 2026-10-02 the user authorized
|
||||
committing/pushing the mission changes and opening a draft PR to `dev` for review.
|
||||
Publication does not imply acceptance, merge, installation, deployment or a shared
|
||||
daemon restart.
|
||||
|
||||
## Scenarios and evidence policy
|
||||
|
||||
- Issue report: Electron Linux `0.20.0-dev-20260930-47f9e43e`, OpenCode 2.0.19,
|
||||
remote Windows backend over HTTP. Submitting a prompt often locks the UI;
|
||||
reopening the client and reconnecting to the same server/session does not help.
|
||||
- Additional user report: native compaction makes the UI and other sessions
|
||||
unresponsive, despite earlier optimization work.
|
||||
- Distinguish renderer CPU, HTTP connection occupancy, backend event-loop work,
|
||||
shared native/plugin work and provider waiting. Compaction and technical-content
|
||||
pruning are different operations.
|
||||
- Use synthetic databases, loopback fixtures and separately launched native
|
||||
runtimes. Never test against the user's OpenCode database or mutate their
|
||||
sessions. A local fixture reproduction is not proof of the reporter's exact
|
||||
root cause or Linux/Electron qualification.
|
||||
|
||||
## Parallel investigations
|
||||
|
||||
1. Prior audit continuity and broader unaddressed performance risks.
|
||||
2. Compaction events, transcript hydration and renderer responsiveness.
|
||||
3. HTTP/SSE, prompt admission and shared backend responsiveness.
|
||||
4. Coordinator: plugin history/index reads and event-loop fairness.
|
||||
|
||||
The previous audit's reports remain in the separate `audit-resume-integration`
|
||||
worktree. Its changes must be checked against this baseline before reuse.
|
||||
|
||||
## Confirmed: structural-index batches monopolize their event loop
|
||||
|
||||
`packages/server/src/opencode/session-pruning/outline-index.ts` originally read
|
||||
512 checkpoint headers synchronously with `.all()`, then projected assistant JSON
|
||||
for 128 rows before yielding. A row count is not a time budget: tool output size
|
||||
varies considerably, and SQLite evaluates nested assistant JSON synchronously.
|
||||
The routine is invoked by the bundled plugin's `outline` RPC.
|
||||
|
||||
### Reproduction
|
||||
|
||||
`scripts/profile-outline-responsiveness.mts` creates only an in-memory SQLite
|
||||
database. It runs the real structural-index reader with 512 assistant messages,
|
||||
each holding one 1 MiB textual tool result. The heartbeat is a separate
|
||||
`setImmediate` callback on the same loop. Its maximum observed gap includes
|
||||
synchronous SQL/JS work and host scheduling, not remote-client paint latency.
|
||||
|
||||
```powershell
|
||||
node --import tsx scripts/profile-outline-responsiveness.mts 512 1048576 --baseline
|
||||
node --import tsx scripts/profile-outline-responsiveness.mts 512 1048576
|
||||
```
|
||||
|
||||
`--baseline` bundles the exact pre-mission `366830f6` reader without replacing
|
||||
working-tree files. Seeding and module compilation are outside the measured scan.
|
||||
The stress corpus is deliberately large (~512 MiB of payload); this is not an
|
||||
estimate of a typical session or the reporter's data size.
|
||||
|
||||
First baseline run: maximum gaps **542 / 522 / 720 ms**, total scans
|
||||
1717 / 1969 / 2209 ms, six heartbeat turns each. A subsequent immutable-source
|
||||
baseline run observed **615 / 755 / 712 ms**, totals 2066 / 2409 / 2273 ms.
|
||||
Initial modified run: maximum gaps **13 / 14 / 14 ms**, totals
|
||||
1829 / 2680 / 2225 ms, 199 / 262 / 224 turns. Windows, Node/SQLite; uncontrolled
|
||||
concurrent host work. These observations demonstrate improved cooperation, not
|
||||
reduced total CPU or an application frame-rate claim. No wall-clock CI threshold.
|
||||
|
||||
### Correction and regression contract
|
||||
|
||||
Iterate the same checkpoint headers and retain the exact digest/page protocol.
|
||||
Yield during both header reads and JSON projection when the slice has consumed
|
||||
8 ms or 128 rows, and check cancellation between rows. Preserve read snapshots,
|
||||
ownership, staged-undo visibility, known checkpoints and response bounds.
|
||||
|
||||
`outline-responsiveness.test.ts` uses real SQLite with a view that charges a
|
||||
deterministic simulated cost per payload access. It schedules an actual pending
|
||||
event-loop cancellation in the header or projection phase. The original header
|
||||
regression failed after **896 payload accesses** before cancellation could run.
|
||||
Both new tests pass after the patch and assert the read transaction is released.
|
||||
The existing nine navigation tests also pass; server typecheck passes.
|
||||
|
||||
A single SQLite step remains synchronous and cannot be interrupted by this
|
||||
cooperative budget. This patch does not move native storage work to another
|
||||
thread, reduce all JSON parsing, or establish that this defect alone causes #824.
|
||||
Native plugin qualification and independent review are tracked separately.
|
||||
|
||||
## Confirmed: subscriber failures cross feature/connection boundaries
|
||||
|
||||
The prior audit found these defects, but both source blobs were unchanged in
|
||||
`366830f6`. They were reproduced again against the current mission baseline.
|
||||
|
||||
- Browser `ServerEvents` dispatch stopped at the first throwing callback. A
|
||||
wildcard subscriber could prevent the real SSEManager and typed subscribers
|
||||
from receiving valid native events; open/status callback failures could prevent
|
||||
reconciliation or abort restart. Three browser regressions failed before the
|
||||
patch; all four (including normal FIFO/unsubscribe) pass after per-callback
|
||||
exception isolation. Faults are diagnosed and never replayed.
|
||||
- `OpenCodeSharedService.invalidateAfterStream` invalidated the shared client in
|
||||
`finally`, including on subscriber-local abort, iterator return/throw or an
|
||||
already-aborted subscription. Four regressions failed with the real SDK and
|
||||
loopback HTTP/SSE source. All seven now pass: local lifetime changes leave the
|
||||
healthy source/client alone, whereas genuine upstream EOF/transport errors
|
||||
still invalidate the originating generation. Late cleanup cannot invalidate a
|
||||
replacement; obsolete iterators cannot publish queued events. The fourteen
|
||||
existing shared-service tests also pass.
|
||||
|
||||
Commands:
|
||||
|
||||
```powershell
|
||||
node --import tsx --test packages/ui/tests/browser/event-subscriber-isolation.test.ts
|
||||
node --import tsx --test packages/server/src/workspaces/subscriber-lifetime.test.ts packages/server/src/workspaces/opencode-service.test.ts
|
||||
```
|
||||
|
||||
The complete private native fixture also passed on OpenCode 2.0.21 with the
|
||||
production shared-service wrapper: abort/return/throw retain the same client,
|
||||
relay shutdown leaves the independent subscriber usable, slow-recipient FIFO
|
||||
remains correct, and proxy/history/navigation/pruning checks pass. Retained
|
||||
fixture: `C:\Users\Admin\AppData\Local\Temp\opencode\codenomad-pruning-native-Atf4fG`.
|
||||
The 48 adjacent relay/SSE/proxy/navigation unit tests pass as well. No user
|
||||
daemon was stopped or restarted.
|
||||
|
||||
These are functional isolation fixes, not measured CPU speedups, and no
|
||||
production callback failure from the reporter has been captured. They prevent
|
||||
one feature/consumer's failure from disturbing unrelated consumers; they do not
|
||||
isolate synchronous CPU-heavy callbacks into different threads.
|
||||
|
||||
Independent event-boundary review approved these corrections without a
|
||||
functional blocker, but reproduced a diagnostic gap: a deferred Solid memo
|
||||
failure can occur at the final shared-batch flush, after callback-level catches.
|
||||
The transport then mislabeled it as invalid JSON. A small follow-up separates
|
||||
JSON decoding from event dispatch in `event-source-handlers.ts`. Its new unit
|
||||
and real-SSE/browser-derived-error tests failed before the patch; all nine focused
|
||||
tests now pass. The same lot's native/typed handlers receive each event once,
|
||||
and subsequent events continue. This does not isolate every reactive computation
|
||||
or change shared batching. Final UI review will recheck it with compaction work.
|
||||
|
||||
Residual limits from `issue-824-event-review.md`: these diagnostics use the
|
||||
existing `sse` debug logger (not enabled by default); a concurrent local abort and
|
||||
physical source failure has not been deterministically reproduced. No speculative
|
||||
connection-invalidation change or new logging pipeline is introduced for them.
|
||||
|
||||
## Confirmed: orphaned HTTP preflight observers
|
||||
|
||||
The real Fastify/loopback proxy reproduction in `issue-824-transport` held the
|
||||
native session-ownership read for prompt and compact requests. Closing the HTTP
|
||||
client did not cancel those reads: listeners were installed only after preflight.
|
||||
An independent session still responded before the held reads were released.
|
||||
This proves an orphaned-request defect, not a global server CPU freeze or why a
|
||||
real native read stalls in the reporter's case.
|
||||
|
||||
The dedicated correction (`d9598e42`, following red diagnostic `1ece148c`)
|
||||
installs per-request cancellation at entry, propagates it to supported reads,
|
||||
retires the local observer of uncancellable shared work, and fences late
|
||||
continuations before environment writes or forwarding. It never treats ordinary
|
||||
POST body completion as disconnection, adds no new execution timeout and never
|
||||
interrupts/replays an admitted native mutation. Twelve real HTTP tests and three
|
||||
scope tests passed in the source worktree and again after integration, along with
|
||||
65 adjacent proxy tests and server typecheck. Independent review accepts the patch;
|
||||
see `issue-824-http-admission-fix.md`.
|
||||
|
||||
## Confirmed: HTTP password derivation blocks unrelated work
|
||||
|
||||
The prior audit's synchronous-scrypt concern was reproduced with real
|
||||
Fastify/AuthManager/auth routes and a private v1 auth file. In five baseline runs,
|
||||
a witness endpoint could not run until all eight invalid logins or four password
|
||||
changes finished. The async-scrypt correction admits the witness while those
|
||||
operations are pending. Descriptive five-process medians: login witness
|
||||
**168.63 → 3.37 ms**, password-change witness **82.43 → 0.80 ms**. Timing is not
|
||||
the CI assertion: the tests require witness progress before the burst completes.
|
||||
|
||||
Hash format/parameters and constant-time comparison are retained; password
|
||||
changes are serialized and recover after persistence failure. The CLI/env
|
||||
override hashes synchronously only at startup, before HTTP serving. The shared
|
||||
libuv pool remains a finite resource, and there is no claim of reduced password
|
||||
CPU or a fix for compaction. Eight focused tests passed repeatedly in the source
|
||||
worktree. Integration added a deterministic regression for an old login crossing
|
||||
a password change: the initial async patch returned 200 after the new secret was
|
||||
stored. The corrected verifier checks that its auth record remains authoritative
|
||||
after the derivation; it returns 401 without a cookie or replay. All nine auth
|
||||
tests and server typecheck pass after integration. Independent review accepts it;
|
||||
see `issue-824-auth-responsiveness.md`.
|
||||
|
||||
## Confirmed: nested task tools remount on every delta
|
||||
|
||||
The prior audit's task-renderer correction was requalified against the identical
|
||||
`366830f6` source blob and integrated with a minimal +29/-18 diff. It retains
|
||||
stable child-tool keys/shells while reading their current payload. Two baseline
|
||||
regressions fail on shell identity and stale Markdown; 63 adjacent browser tests
|
||||
pass after correction, covering disclosure, nested scroller state, in-place
|
||||
revision, removal, copy, errors/retry and tool images.
|
||||
|
||||
Current bounded A/B (30 samples per variant, instrumentation off): median
|
||||
main-thread duration **353.39 → 22.72 ms**. Instrumented controls show 80 tool-shell
|
||||
removals/reinsertions per delta becoming zero. The 80 clones/reloads remain; no
|
||||
claim is made about desktop frame rate or eliminating all task-render costs.
|
||||
See `issue-824-render-recovery.md` for artifacts, failed setup runs excluded from
|
||||
statistics, and source-drift caveats. Final combined validation must use a frozen
|
||||
composition; the prior measurements do not establish that the whole concurrent
|
||||
worktree was unchanged. Independent combined UI review confirmed a membership
|
||||
defect: an authoritative page removing one tool part from a retained message
|
||||
leaves a phantom key/count, potentially hiding a later replacement. The renderer
|
||||
correction therefore was not accepted for final qualification. The follow-up now
|
||||
reuses the existing bounded structural scanner at each session revision, retaining
|
||||
the prior ordered key list when unchanged. Keyed `For` preserves retained shells
|
||||
through removal/reordering. Five new regressions fail on the preimage and all
|
||||
68 adjacent browser scenarios pass naturally after correction, including exact
|
||||
membership/count, tool-to-text identity changes and truncation shrink. The scan
|
||||
still caps at 10,000 units and 201 tool discoveries/200 displayed tools; scanning
|
||||
IDs/types on every revision is the explicit correctness tradeoff. Zero remounts
|
||||
per delta remain instrumentally verified, but prior CPU A/B figures do not
|
||||
qualify this new postimage. Independent re-review now closes M1 with no residual
|
||||
blocker: 13 renderer and nine copy/image cases pass freshly and naturally, with
|
||||
zero remounts per delta. See
|
||||
`issue-824-task-membership-fix.md` and `issue-824-ui-review.md`, finding M1.
|
||||
|
||||
## Confirmed: compaction payload reduction precedes visible throttling
|
||||
|
||||
The existing 250 ms throttle limited only visible projection; every native
|
||||
fragment still entered the reactive SDK reducer, even for an inactive session.
|
||||
The correction aggregates by instance/session before that reducer, with a timer
|
||||
started by the first chunk (later chunks do not postpone it). Incoming fragments
|
||||
still advance revision/read fences immediately. Same-session ordering and SDK
|
||||
page boundaries flush pending text; terminal authority, reconnect, deletion,
|
||||
revert, pruning and disposal cancel obsolete buffers. Never-loaded inactive
|
||||
sessions keep authority markers rather than materializing summary payloads.
|
||||
|
||||
The acceptance tests now use exact emission counts and text, not the original
|
||||
artificial 32 MiB Long Task threshold: 128 active chunks produce one interval
|
||||
reduction instead of 128, eight chunks delivered in separate 10 ms tasks produce
|
||||
one reduction, and 128 never-loaded inactive chunks produce no payload reduction
|
||||
or history request. All five browser tests discriminate the exact baseline and
|
||||
pass on the patch. Activation mid-compaction, loaded-inactive sessions and remount
|
||||
preserve exact authoritative text. Eleven new reducer/lifecycle tests and 130
|
||||
neighbors pass; 67 real-renderer/history browser cases pass naturally.
|
||||
|
||||
See `issue-824-compaction-ui-fix.md`. Unit store gates use the repository's
|
||||
`--conditions=browser --test-force-exit` convention for persistent imported
|
||||
timers; this is not evidence of natural process cleanup. Spaced dispatcher tasks
|
||||
are not real EventSource/provider cadence qualification. Interleaved same-session
|
||||
events/read boundaries intentionally reduce the coalescing ratio, and sustained
|
||||
arrivals can delay the existing revision-stable activation catch-up. Independent
|
||||
UI review accepts this coalescing within those limits, without a reproduced text,
|
||||
ordering or fence defect. Frozen-source validation results are recorded below.
|
||||
|
||||
## Browser HTTP/1 isolation: bounded non-reproduction
|
||||
|
||||
Five real Chromium Windows → Fastify Windows loopback HTTP/1.1 cases pass:
|
||||
prompt/compact crossed with page/context close, plus a separate background-read
|
||||
budget case. One real multiplexed SSE and a held SDK session A preflight leave
|
||||
input/switch, B reads, metadata and a control endpoint available. Abandonment and
|
||||
reconnection retire old HTTP/SSE observers before the held handler is released;
|
||||
late completions produce no forward, environment write or shared invalidation.
|
||||
Five catalogue intents admit only two held reads; queued work from the destroyed
|
||||
page does not resume. Held upstream handler closures and idle pooled sockets are
|
||||
reported separately from outstanding HTTP observers.
|
||||
|
||||
See `issue-824-browser-http.md`: 5/5 standalone, 10/10 combined with event
|
||||
isolation, and 15/15 neighboring server tests pass naturally. No new product
|
||||
correction follows from this non-reproduction. CDP and Fastify both verify the
|
||||
HTTP/1.1 protocol, but controls are a minimal Solid fixture, not the complete
|
||||
transcript/composer; upstream is disposable HTTP with native-shaped responses,
|
||||
not native compaction. Linux/Electron, LAN and native 2.0.19 remain unqualified;
|
||||
the report records the isolated cross-host experiment and evidence to collect.
|
||||
|
||||
## Native service isolation: bounded non-reproduction
|
||||
|
||||
The private native compaction fixture completed twice on OpenCode 2.0.21/Windows.
|
||||
While A's real summary response was deliberately gated for ~1.5 s, B accepted a
|
||||
prompt, ran its model request and completed successfully before A was released.
|
||||
Native info/get/activity remained available (~1.8–7 ms observed). Both sessions
|
||||
were simultaneously running; A then completed compaction with 29 native deltas
|
||||
and no compaction-failed event. During a separate 32 MiB outline scan in the real
|
||||
plugin host, six native info reads completed before the scan ended (final run).
|
||||
|
||||
See `issue-824-native-compaction.md` for commands, evidence and limits. This first
|
||||
scenario used only 24,557 bytes of outgoing summary context; the 32 MiB tool
|
||||
corpus belongs to outline work, not A's compaction context. Large-context assembly
|
||||
was then qualified separately: two private runs each at 2/8/32 MiB verify every
|
||||
tool payload byte at the actual provider boundary (32 MiB request JSON:
|
||||
33,612,826 bytes). Submission-to-provider headers grows to 225–291 ms at 32 MiB;
|
||||
unrelated pre-arrival reads show transient latency up to ~85 ms. B nevertheless
|
||||
completes before A reaches the provider in both 8/32 MiB pairs, and completes
|
||||
before A's held response is released at every size. No continuous global freeze
|
||||
or specific native CPU hotspot is established. Provider JSON parse time is
|
||||
separately recorded, not attributed to native assembly. No speculative upstream
|
||||
patch follows. No inference about 2.0.19, Linux/Electron, remote TCP or the
|
||||
reporter's exact data follows from this bounded evidence.
|
||||
|
||||
## Validation checkpoint (not final combined qualification)
|
||||
|
||||
`node --import tsx --test "packages/server/src/**/*.test.ts"` completed naturally:
|
||||
**776 tests, 770 pass, 6 skip, 0 fail**, about 32.8 s. UI and server typechecks
|
||||
also passed at this checkpoint. Auth/proxy corrections developed in separate
|
||||
worktrees are not included in that run; fresh validation follows integration.
|
||||
|
||||
After auth and HTTP integration, a fresh strict-unhandled-rejection full server
|
||||
run completed naturally: **799 tests, 793 pass, 6 skip, 0 fail**, about 32.8 s.
|
||||
Command: `node --unhandled-rejections=strict --import tsx --test
|
||||
"packages/server/src/**/*.test.ts"`. Log: managed shell
|
||||
`sh_0f87b4309001T4M1Bgo7XpAvfz.out`. This includes the new login-authority race
|
||||
regression; counts are not additive with earlier runs. Final review and frozen
|
||||
composition validation remain outstanding.
|
||||
|
||||
The complete server build also passes (`npm run build --workspace
|
||||
@neuralnomads/codenomad`), including production UI, TypeScript, auth assets and
|
||||
bundled pruning/automation plugins. UI and Electron typechecks pass afterward;
|
||||
diff check reports no whitespace defects. Vite reports chunks above 500 kB; no
|
||||
unrelated bundling policy change or warning suppression was introduced. This
|
||||
build did not start or deploy a desktop/backend process.
|
||||
|
||||
The first complete browser run finished naturally: **349 tests, 348 pass, 1 skip,
|
||||
0 fail**, about 22.2 minutes (`npm run test:browser --workspace @codenomad/ui`,
|
||||
log `sh_0f86cb7bb001g0xkd6q5qnv4G2.out`). It includes the five compaction cases,
|
||||
task-shell retention, renderer/history/navigation and native Electron fixture
|
||||
checks. The opt-in Electron tab-chrome check is skipped. Because this run began
|
||||
while source owners were still integrating, it is not a frozen-source final
|
||||
qualification; its event-isolation file executed before the additional deferred
|
||||
Solid error case, and the later browser HTTP fixture was not included. Do not
|
||||
reuse or add its count to the forthcoming fresh composition gate.
|
||||
|
||||
The independent server integration review accepts auth and HTTP changes without
|
||||
a blocking finding; see `issue-824-server-review.md`. It preserves the distinction
|
||||
between proven local defects and the unqualified reporter scenario. UI review
|
||||
accepts compaction and SSE diagnostics; its task-membership finding was corrected
|
||||
and closed by independent re-review. Dedicated HTTP browser checks are complete.
|
||||
Fresh combined validation subsequently finished with before/after source fingerprints
|
||||
covering tracked and untracked executable inputs. Its acceptance verdict and later
|
||||
bounded follow-ups are below; documentation updates are excluded from its source
|
||||
manifest.
|
||||
|
||||
## Reviewed-composition validation: acceptance remains blocked
|
||||
|
||||
The fresh validation uses 1,696 tracked and untracked executable inputs. Completed
|
||||
gates have identical before/after manifests, SHA256
|
||||
`16a3d4085dac90c27e892a480ae4c0735eb52782a9add8b0ca6732a640f1a4df`.
|
||||
This verifies their boundaries, not a filesystem lock or installed dependencies.
|
||||
Exact commands, logs and limitations are in `issue-824-combined-validation.md`;
|
||||
private artifacts remain under
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/issue-824-combined-b91c8b66fcc840e1909170c5eeb846b9/`.
|
||||
|
||||
- Full server replay: **799 tests, 793 pass, 6 skip, 0 fail**, natural exit 0.
|
||||
The first run's private Git-directory cleanup `EPERM` (792 pass, 1 fail, 6 skip)
|
||||
is retained; the isolated nine Git tests and full replay pass with unchanged
|
||||
sources. Its Windows cause is not established.
|
||||
- UI/server/Electron typechecks and server build (including UI and bundled
|
||||
plugins) pass naturally. A fresh complete private native fixture passes on
|
||||
OpenCode 2.0.21; no user runtime or storage was used.
|
||||
- Stores: **141/141 assertions pass with force-exit**. The natural-exit attempt
|
||||
for the new compactage tests prints 11 passes but times out after 30 seconds;
|
||||
lifecycle cleanup is not qualified. Four event-handler tests exit naturally.
|
||||
- Full browser: **360 tests, 356 pass, 3 fail, 1 skip**, natural exit 1 in about
|
||||
21.6 minutes. Failures are focus not returning to the composer after successful
|
||||
Git diff insertion (line 609), MCP fixture readiness at reduced motion/150%
|
||||
scale, and the expected legacy cached HTML not appearing after the literal-mode
|
||||
toggle (line 104; earlier HTML/source checks passed).
|
||||
All three pass in a targeted natural-exit replay without source edits; that
|
||||
does **not** convert the full suite into a passing gate or establish the causes.
|
||||
The existing focused replay has also finished naturally: **104/104 pass**, no
|
||||
skip/cancelled, unchanged sources. Its eight suites include all mission browser
|
||||
gates and the full session-rendering file. The validator's terminal report is
|
||||
received; validation execution is complete, acceptance is still non-green.
|
||||
- Fresh renderer A/B on the final membership-corrected postimage: three couples,
|
||||
30 samples per variant, instrumentation off, natural exit 0. Median main-thread
|
||||
**324.57 → 16.94 ms** (baseline range 168.06–743.53 ms, fixed 9.93–50.33 ms).
|
||||
Raw metrics: `C:/Users/Admin/AppData/Local/Temp/opencode/codenomad-render-cost-srSQOf/metrics.json`.
|
||||
This supersedes the earlier postimage's CPU qualification, not its historical
|
||||
evidence; it measures the bounded fixture, not desktop FPS or all task costs.
|
||||
|
||||
No functional review blocker remains. However, the fresh full-browser gate is
|
||||
non-green and the reporter's Electron Linux → Windows LAN/HTTP/OpenCode 2.0.19
|
||||
scenario remains unqualified. The original freeze's exact cause is not established;
|
||||
the private 2/8/32 MiB native compaction and loopback HTTP fixtures did not reproduce
|
||||
a persistent global freeze. Do not mark #824 universally resolved or publish/install
|
||||
these changes from this evidence alone.
|
||||
|
||||
The independent `browser-focus-cache-diagnosis` is now complete without editing
|
||||
sources. It identifies a test synchronization gap: insertion updates the value
|
||||
immediately, but composer focus/selection run in `setTimeout(0)`; the test asserts
|
||||
focus immediately after reading the value. The original failed instant is allowed
|
||||
by that contract; persistent loss of focus or draft is not demonstrated. Three
|
||||
new complete focus passes and three HTML-cache passes are recorded separately.
|
||||
A fourth focus attempt fails earlier, during a detached/intercepted Monaco overlay
|
||||
click, and is retained as a distinct failure rather than attributed to #824.
|
||||
The HTML cache transition is a synchronous local cache hit, so ordinary slow
|
||||
Markdown highlighting is not its explanation; the original timeout remains
|
||||
unattributed. See `issue-824-browser-focus-cache-diagnosis.md`.
|
||||
|
||||
The bounded `browser-mcp-bootstrap-diagnosis` is also complete: readiness timed
|
||||
out in one of five natural-exit runs, with unchanged executable sources. The
|
||||
network observations saturated the private harness's shared 4,000-entry budget
|
||||
before terminal stages/errors/snapshots, so attribution is still impossible.
|
||||
This is a confirmed diagnostic-capture weakness, not an identified product cause.
|
||||
See `issue-824-browser-mcp-bootstrap-diagnosis.md`; all five outcomes are retained.
|
||||
|
||||
`browser-focus-test-sync` has now completed. The coordinator reviewed the diff:
|
||||
after the unchanged text assertions, the test captures the textarea and waits for
|
||||
its identity to equal `document.activeElement`, then retains the original identity
|
||||
assertion. No sleep, new timeout, force-click, retry or product edit. Focus passes
|
||||
2/2; the two-case run passes once and fails once on the independent HTML-cache
|
||||
timeout. That timeout is now reproduced, not merely an unknown historical failure.
|
||||
Opt-in `CODENOMAD_BROWSER_BOUNDARY_TRACE=1` prepares cache/overlay/focus diagnostics;
|
||||
it was added after those two runs and has not yet captured the cache recurrence.
|
||||
See `issue-824-browser-focus-test-sync.md`. Three authorized test/fixture inputs
|
||||
changed; the earlier combined manifests remain historical, not fingerprints of
|
||||
the revised test composition. Four selected product inputs remain unchanged in
|
||||
the specialist's focused manifest; that subset is not a full-product manifest.
|
||||
|
||||
`browser-mcp-critical-capture` is now terminal: the private collector's overflow
|
||||
is verified without a browser, then **5/5 targeted runs pass naturally** in 54.35 s
|
||||
with complete critical traces, zero overflow and zero executable-source drift.
|
||||
The 1,696-input comparison confirms that only the three authorized focus-sync
|
||||
test/fixture paths differ from the earlier combined validation. Settings and render
|
||||
finish in tens of milliseconds in these passes; most observed navigation time is
|
||||
before the fixture body. That localizes cost in passes, not the historical timeout.
|
||||
No sixth targeted run or product patch is justified. The prior red MCP and recurrent
|
||||
HTML-cache timeout remain open; see `issue-824-browser-mcp-critical-capture.md`.
|
||||
|
||||
`browser-final-boundary-gate` has finished its **single normative full-browser
|
||||
run**: **360 tests, 358 pass, 1 fail, 1 skip, 0 cancelled**, natural exit 1 in
|
||||
20 min 10 s. All 1,696 inputs remain identical before/after, new manifest SHA256
|
||||
`da74db30b68829edd40b44c069191cba8b21cc3e5e5d345862eec0cb9a14eb1c`.
|
||||
The only failure is the final `errors=[]` assertion in the Git insertion test:
|
||||
`ReferenceError: __name is not defined` from the anonymous opt-in browser script.
|
||||
All earlier functional assertions in that case, including focus, pass in this
|
||||
execution. The trace array is present but empty; it cannot prove absent gestures.
|
||||
MCP and HTML-cache cases pass here, without attributing their earlier failures.
|
||||
No retry or another full suite was run. See `issue-824-browser-final-boundary-gate.md`.
|
||||
|
||||
Acceptance remains **non-green**. `browser-trace-serialization-fix` is now
|
||||
complete: the exact callback imported through the installed `node --import tsx`
|
||||
serializes `const describe=__name(...)` without the module-scope helper. A minimal
|
||||
Chromium page reproduces the same exception and empty trace. This is a confirmed
|
||||
test-instrumentation defect introduced during this mission, not a product defect.
|
||||
It is corrected with explicit, self-contained JavaScript passed to
|
||||
`addInitScript({ content })`. The minimal Chromium check passes with no pageerror,
|
||||
all observation kinds present and 200 entries retained after saturation. Snapshot
|
||||
failure is isolated from the test assertion and page closure. UI typecheck and
|
||||
diff-check pass; assertions, gestures, timeouts and product remain unchanged.
|
||||
See `issue-824-browser-trace-serialization-fix.md`. The recorded full gate stays
|
||||
red; there has been **no full-suite or feature-case rerun** after this correction.
|
||||
|
||||
Coordinator checkpoint, 2026-10-02 00:03 CEST: all **26 assigned tasks are
|
||||
terminal completed**, no outstanding task, and no matching browser runner was
|
||||
observed in the read-only Windows process check. A fresh hash comparison of all
|
||||
**1,696 executable inputs**, including current tracked/untracked paths, against
|
||||
the final gate finds exactly one difference: `git-history.test.ts`, SHA256
|
||||
`dc7c378cc15e54f22f5d7e1a4c485313f0af9df536e1544690a33e3b2b2e5264`.
|
||||
No product, configuration or script drift is found. This checkpoint is not a new
|
||||
test execution or retroactive all-green verdict.
|
||||
|
||||
Next decision, not work running in the background: an acceptance run on the
|
||||
corrected test composition is still needed for an all-green full-browser claim.
|
||||
No automatic retry, extra audit or deployment is launched at this checkpoint.
|
||||
The exact original #824 cause, earlier MCP/cache failures and natural store
|
||||
cleanup remain unqualified; the mission is not finished as a successful fix.
|
||||
Cross-host traces remain to be collected
|
||||
according to `issue-824-browser-http.md`. No new product patch is justified solely
|
||||
by a passing retry. No commit/push/merge/install/restart/session cleanup is
|
||||
authorized by this checkpoint.
|
||||
|
||||
## Draft PR handoff authorized by the user
|
||||
|
||||
2026-10-02: the user requested a PR containing all mission changes. Deliver the
|
||||
63 reviewed product/test/script/report paths on `fix/issue-824-performance` to
|
||||
`dev` as a **draft**, without a closing keyword for #824. Preserve every recorded
|
||||
failed run and qualification limit. The draft is the reviewable deliverable, not
|
||||
a claim that all gates pass or the reported freeze is universally resolved.
|
||||
|
||||
All 26 investigation/correction/review tasks are complete and no test runner is
|
||||
active at the handoff checkpoint. The durable mission's nonterminal status does
|
||||
not mean that an agent or command is still executing: acceptance is incomplete.
|
||||
No automatic retry or extra general audit accompanies publication. Acceptance
|
||||
still needs the corrected test composition qualified and the cross-host limits
|
||||
resolved or explicitly accepted; merge/install/restart/session cleanup remain
|
||||
outside this publication authorization.
|
||||
|
||||
Modified source files above the size guideline (no size-only refactor):
|
||||
`packages/server/src/server/http-server.ts` ~2,346 lines,
|
||||
`packages/server/src/workspaces/manager.ts` ~1,240,
|
||||
`packages/ui/src/stores/instances.ts` ~2,093,
|
||||
`packages/ui/src/stores/opencode-data.ts` ~828,
|
||||
`packages/ui/src/components/tool-call/renderers/task.tsx` ~569.
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
# Issue #824 — continuité de l'audit performance
|
||||
|
||||
- Date : 2026-10-01
|
||||
- Baseline examinée : `366830f6` (`origin/dev`)
|
||||
- Audit repris : worktree `audit-resume-integration`, baseline `47f9e43e`
|
||||
|
||||
## Portée et niveaux de preuve
|
||||
|
||||
Diagnostic en lecture seule du produit. Aucun daemon, profil, desktop ou stockage
|
||||
utilisateur n'a été démarré ou modifié. Le seul exercice exécuté ici est un micro-
|
||||
benchmark local de la primitive d'authentification, sans réseau ni fichier partagé.
|
||||
|
||||
Les affirmations ci-dessous portent explicitement l'un des niveaux suivants :
|
||||
|
||||
- **Mesure exécutée** : observation chronométrée pendant cette mission ou mesure A/B
|
||||
conservée par l'audit précédent avec protocole et artefact décrits.
|
||||
- **Reproduction fonctionnelle antérieure** : assertion rouge/verte exécutée par
|
||||
l'audit précédent, sans nouvelle mesure de latence dans cette mission.
|
||||
- **Analyse statique** : chemin de code observé ; coût ou impact encore à mesurer.
|
||||
- **Hypothèse** : mécanisme plausible à tester, pas un défaut confirmé.
|
||||
|
||||
Les diagnostics spécialisés de compactage et de transport HTTP Linux/Windows sont
|
||||
hors de cette carte. Les priorités proposées sont transversales et complémentaires.
|
||||
|
||||
## Continuité entre l'audit et la baseline courante
|
||||
|
||||
L'audit précédent n'était pas un commit : `audit/resume-integration` est toujours à
|
||||
`47f9e43e` avec ses corrections en fichiers modifiés/non suivis. Entre `47f9e43e` et
|
||||
`366830f6`, les 16 commits de `origin/dev` concernent notamment Files, responsive,
|
||||
usage, OpenCode 2.0.20/2.0.21 et Tauri macOS. Ils ne constituent pas l'intégration
|
||||
générale de l'audit.
|
||||
|
||||
Contrôle objet Git effectué : les dix fichiers sentinelles suivants ont exactement
|
||||
le même blob à `47f9e43e` et à `366830f6` :
|
||||
|
||||
| Surface auditée | Blob encore présent à `366830f6` | État |
|
||||
| --- | --- | --- |
|
||||
| rendu des tâches imbriquées, `packages/ui/src/components/tool-call/renderers/task.tsx` | `dee90df8560b83958dbf6d6f7c40e013bbdb289f` | correction audit absente |
|
||||
| distribution SSE UI, `packages/ui/src/lib/server-events.ts` | `29d8342071fee3fc1ab3a2f7bc71c847bc68f8a8` | correction audit absente |
|
||||
| itérateurs/relay OpenCode, `packages/server/src/workspaces/opencode-service.ts` | `ad8cfbaa00ebce3cc47e9a70217244217cb8b3dd` | correction audit absente |
|
||||
| Speech/Sidecars, `packages/ui/src/stores/speech.ts`, `sidecars.ts` | `45832e4c…`, `d52a8d5f…` | corrections audit absentes |
|
||||
| fermeture multi-fenêtres Electron, `packages/electron-app/electron/main/multiwindow-lifecycle.ts` | `59995dfd1b36cc4d4eee742c4798477cb5b59b9b` | correction audit absente |
|
||||
| historique pruning, `packages/server/src/opencode/session-pruning/history-database.ts` | `37318db55235fb9b50ffd2efb60a154495f6967a` | correction audit absente |
|
||||
| polling provider, `packages/ui/src/components/provider-auth/provider-manager-modal.tsx` | `c1e977a70acaa649ab81d3dba5d6019c693f4f09` | correction audit absente |
|
||||
| lecture conversation, `packages/ui/src/stores/conversation-speech.ts` | `67a12d80c070874f222b24b7b44dfd400beb51a6` | correction audit absente |
|
||||
| auth manager, `packages/server/src/auth/manager.ts` | `4e234ace6fc1b94ebb8543617d7d8048239135db` | correction audit absente |
|
||||
|
||||
`packages/server/src/server/http-server.ts` a changé dans #811/#814, mais la
|
||||
comparaison avec le worktree d'audit montre encore un delta : ces commits ne sont
|
||||
pas l'intégration de la composition preview/redaction auditée.
|
||||
|
||||
### Ce que l'audit avait réellement qualifié
|
||||
|
||||
- **Rendu imbriqué** : mesure A/B historique sur 80 étapes `read` de 8 000 caractères,
|
||||
30 échantillons par variante. Médiane main-thread `369,98 -> 20,64 ms` par delta,
|
||||
remounts DOM `80 retirés + 80 insérés -> 0`; 80 snapshots natifs restent. Protocole,
|
||||
variance et artefact dans `AUDIT_RENDERING_COST.md:40-80`. Deux assertions rouges
|
||||
sur la source baseline et 63 tests navigateur verts sur la correction
|
||||
(`AUDIT_RENDERING_COST.md:128-145`). **Mesure historique, pas rejouée sur 366830f6.**
|
||||
- **Isolation événements** : 50 tests SDK/HTTP/relay/server et 4 régressions Chromium
|
||||
verts sur la correction ; un destinataire relay lent n'y bloque pas le destinataire
|
||||
rapide (`AUDIT_UPSTREAM_DEV_2026_09_30.md:193-208`). La correction isole exceptions
|
||||
et fins locales d'itérateur, pas un callback UI qui consomme le CPU.
|
||||
**Reproduction fonctionnelle antérieure, pas mesure de latence.**
|
||||
- Les correctifs Speech/Sidecars, provider polling, pruning/history et fermeture
|
||||
concurrente ont eux aussi été qualifiés dans le worktree d'audit, mais aucun signal
|
||||
ne permet de les compter comme intégré à `366830f6`. Ils restent des correctifs de
|
||||
robustesse/lifecycle, pas les premières reproductions performance de #824.
|
||||
- L'audit a réfuté dans son fixture réel le montage de tout le transcript/timeline et
|
||||
le DOM non borné des gros outputs (`AUDIT_RENDERING_COST.md:83-100`). Ne pas rouvrir
|
||||
ces trois pistes sans nouvelle preuve contraire.
|
||||
|
||||
## Priorités résiduelles bornées
|
||||
|
||||
### P1 — Rejouer puis intégrer la correction de rendu des tâches imbriquées
|
||||
|
||||
**Niveau : mesure historique confirmée ; correction absente de la baseline.**
|
||||
|
||||
Le code courant recrée le rendu complet via un `createMemo` dans
|
||||
`task.tsx:54-99`, et vide encore les clés à `task.tsx:236-243` pendant une
|
||||
réindexation. Chaque `ToolCall` clone aussi profondément son entrée à la création et
|
||||
à chaque changement de versions (`packages/ui/src/components/tool-call.tsx:630-640`).
|
||||
Cela correspond exactement au chemin mesuré par l'audit. Le coût résiduel des 80
|
||||
`structuredClone` est lui-même mesuré, mais son optimisation n'est pas validée.
|
||||
|
||||
**Plus petite prochaine reproduction :** récupérer sans l'élargir le fixture
|
||||
`scripts/test-render-cost-audit.mjs` et `packages/ui/tests/browser/render-cost.test.ts`
|
||||
du worktree audit, exécuter d'abord l'override rouge sur le blob
|
||||
`dee90df8560b…`, puis A/B sur `366830f6` et sur la correction auditée. Conserver les
|
||||
assertions d'identité DOM comme gate ; ne pas introduire de seuil timing CI instable.
|
||||
|
||||
### P2 — Isoler les abonnés SSE et mesurer le temps de distribution
|
||||
|
||||
**Niveau : défaut fonctionnel reproduit antérieurement ; risque CPU statique.**
|
||||
|
||||
La baseline distribue ouverture, événements et statuts avec des `Set.forEach`
|
||||
synchrones sans frontière par callback (`packages/ui/src/lib/server-events.ts:69-76`,
|
||||
`:151-170`). L'audit a confirmé qu'une exception d'un abonné supprime les abonnés
|
||||
suivants et peut interrompre un restart. Cela peut ressembler à une application figée
|
||||
alors que le transport reste vivant. En revanche, aucune preuve ne montre encore un
|
||||
callback lent bloquant durablement le renderer ; l'audit précise que ce cas n'est pas
|
||||
isolé.
|
||||
|
||||
**Plus petite prochaine reproduction :** réimporter le fixture
|
||||
`event-subscriber-isolation.test.ts`, prouver rouge/vert sur l'exception, puis ajouter
|
||||
un lot borné d'événements avec un abonné instrumenté volontairement lent. Mesurer
|
||||
durée de `dispatchBatch`, retard d'un `requestAnimationFrame` sentinelle et abonnés
|
||||
appelés. Le premier correctif utile reste la petite barrière `try/catch` déjà auditée ;
|
||||
une planification asynchrone ne doit être envisagée qu'après mesure car elle change
|
||||
l'ordre/la sémantique Solid `batch`.
|
||||
|
||||
### P3 — Retirer `scryptSync` du thread HTTP d'authentification
|
||||
|
||||
**Niveau : mesure exécutée et analyse du chemin HTTP.**
|
||||
|
||||
`verifyPassword` et `hashPassword` utilisent `crypto.scryptSync`
|
||||
(`packages/server/src/auth/password-hash.ts:23-28`, `:37-48`). Le POST login appelle
|
||||
ce chemin synchroniquement via `AuthManager.validateLogin`
|
||||
(`packages/server/src/auth/manager.ts:74-79`; route dans
|
||||
`packages/server/src/server/routes/auth.ts:99+`). Pendant ce calcul, le thread Node
|
||||
partagé ne sert ni autres requêtes ni événements des autres sessions.
|
||||
|
||||
Micro-benchmark exécuté sur Windows, Node `v25.2.1`, paramètres de production
|
||||
`N=16384,r=8,p=1`, 10 vérifications séquentielles : médiane **19,56 ms**, plage
|
||||
**19,12–21,60 ms**. C'est une durée de blocage de primitive, pas une mesure Fastify,
|
||||
desktop ou charge concurrente. Une rafale de tentatives multiplie directement ce
|
||||
blocage ; l'impact réel et les limites de taux ne sont pas qualifiés ici.
|
||||
|
||||
**Plus petite prochaine reproduction :** fixture Fastify inject/loopback isolée avec
|
||||
un heartbeat de latence pendant une rafale bornée de logins invalides. Mesurer p50/p95
|
||||
du heartbeat et durée login avant/après passage à `crypto.scrypt` asynchrone, en
|
||||
conservant comparaison constante, paramètres et réponses d'auth identiques. Couvrir
|
||||
aussi `setPassword`, qui appelle `hashPassword` sur le même thread.
|
||||
|
||||
### P4 — Borner le coût renderer + disque de la persistance d'état client
|
||||
|
||||
**Niveau : analyse statique ; hypothèse de freeze à reproduire.**
|
||||
|
||||
Le renderer recapture l'état après 100 ms d'activité
|
||||
(`packages/ui/src/lib/hooks/use-app-session-capture.ts:124-168`), puis la couche état
|
||||
reprogramme une sauvegarde après 250 ms (`packages/ui/src/stores/client-state.ts:10`,
|
||||
`:85-128`, `:139-145`). Chaque sauvegarde normalise, encode tout le graphe et contrôle
|
||||
jusqu'à 4 096 partitions / 256 MiB autorisés
|
||||
(`client-state-partitions.ts:8-15`, `:188-220`). Côté Electron, chaque mutation clone
|
||||
l'enveloppe avec `JSON.parse(JSON.stringify(...))`, la resérialise, l'écrit, rename et
|
||||
fsync le répertoire (`packages/electron-app/electron/main/client-state.ts:628-692`).
|
||||
Le chemin normal est asynchrone côté disque ; les migrations/cross-host utilisent
|
||||
encore des opérations sync, mais surtout au démarrage/récupération, pas à chaque save.
|
||||
|
||||
Aucun profil de gros état ou disque lent n'a été exécuté : **ne pas décrire cette
|
||||
piste comme cause confirmée**.
|
||||
|
||||
**Plus petite prochaine reproduction :** fixture Electron/module isolée générant des
|
||||
états réalistes à 1, 16, 64 et 256 MiB, avec churn de draft/layout à 10 Hz. Instrumenter
|
||||
séparément `encodeClientSnapshotV2`, IPC, file d'écriture, retard event-loop renderer
|
||||
et main, taille/compte de partitions et durée de flush. Simuler un writer lent injecté,
|
||||
sans disque utilisateur. Le résultat dira s'il faut du partage structurel, un worker,
|
||||
une fréquence moindre ou uniquement une borne plus basse.
|
||||
|
||||
### P5 — Mesurer la rafale périodique de réconciliation multi-instance
|
||||
|
||||
**Niveau : analyse statique ; hypothèse de contention réseau/réactive.**
|
||||
|
||||
Toutes les 30 secondes, `App.tsx:276-289` parcourt simultanément toutes les instances
|
||||
`ready`. Même sans session active, `runPendingRequestLiveness` finit par
|
||||
`syncPendingRequests` (`packages/ui/src/stores/instances.ts:864-887`), lequel lance en
|
||||
parallèle scans permissions et Forms, avec jusqu'à trois reprises de stabilisation
|
||||
(`instances.ts:795-817`). Les appels sont coalescés par instance, mais pas étalés entre
|
||||
instances. Des horloges UI à 1 s existent aussi dans `session-list.tsx:117-121`,
|
||||
`instance-shell2.tsx:375-379` et `instance-tab.tsx:28`; leur coût n'est pas mesuré.
|
||||
|
||||
**Plus petite prochaine reproduction :** monter 1/5/20 instances contre clients fake,
|
||||
compter requêtes et concurrence au tick 30 s, puis mesurer retard RAF et commits Solid
|
||||
avec zéro puis plusieurs Forms/permissions. Tester visibilité/inactivité séparément.
|
||||
Ne modifier intervalle, staggering ou demande visible qu'après avoir établi une pente
|
||||
avec le nombre d'instances.
|
||||
|
||||
## Ordre recommandé
|
||||
|
||||
1. Rejouer P1 : gain historique majeur et patch déjà borné/qualifié.
|
||||
2. Rejouer P2 : évite les faux freezes d'état sans toucher au transport.
|
||||
3. Corriger P3 si le test Fastify confirme le retard concurrent ; le mécanisme bloquant
|
||||
est déjà mesuré.
|
||||
4. Profiler P4 et P5 en parallèle conceptuel, mais ne promouvoir un correctif qu'avec
|
||||
une mesure par phase et un fixture de non-régression.
|
||||
|
||||
Cette carte ne prétend pas expliquer le freeze de compactage ni le blocage HTTP
|
||||
Linux/Windows persistant après restart. Elle évite également de compter comme
|
||||
intégrées les corrections seulement présentes dans `audit/resume-integration`.
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
# #824 — coopération du serveur pendant les calculs de mot de passe
|
||||
|
||||
## Périmètre et résultat
|
||||
|
||||
Tâche initiale `auth-loop-responsiveness`, branche `fix/issue-824-auth`, worktree
|
||||
`D:\CodeNomad\.codenomad\worktrees\issue-824-auth`, baseline `366830f6`.
|
||||
Intégration `integrate-auth` dans
|
||||
`D:\CodeNomad\.codenomad\worktrees\issue-824-performance` : sept fichiers
|
||||
autorisés seulement, via patch tool, sans réinstallation des dépendances.
|
||||
|
||||
Défaut confirmé puis corrigé : les routes de login et de changement de mot de
|
||||
passe exécutent une dérivation scrypt coûteuse sur le thread HTTP partagé.
|
||||
Les dérivations HTTP utilisent maintenant `crypto.scrypt`, sur le pool libuv,
|
||||
et leurs callers attendent le résultat. Aucun changement logout, compaction,
|
||||
transport SSE, daemon OpenCode ou configuration utilisateur par ces tâches.
|
||||
|
||||
## Reproduction comportementale rouge
|
||||
|
||||
`packages/server/src/server/routes/auth-responsiveness.test.ts` utilise le vrai
|
||||
Fastify, les vraies routes et un vrai AuthManager. Chaque fixture crée un
|
||||
`auth.json` v1 privé sous `%TEMP%/opencode/codenomad-auth-responsive-*`, avec un
|
||||
hachage produit indépendamment par l'ancienne API synchrone. Elle ferme Fastify
|
||||
et supprime uniquement son propre répertoire à la fin.
|
||||
|
||||
Deux rafales bornées : 8 logins invalides, puis 4 changements authentifiés de
|
||||
mot de passe. À l'entrée du premier calcul, un `setImmediate` programme un GET
|
||||
sur un endpoint témoin, qui observe le nombre d'opérations encore en cours.
|
||||
L'assertion exige que le témoin s'exécute **avant la fin de tous les calculs**.
|
||||
Les durées ne sont que des diagnostics, sans assertion de seuil en ms.
|
||||
Le corps des méthodes, le calcul cryptographique et les handlers ne sont pas
|
||||
remplacés ; les wrappers ne font que compter et chronométrer.
|
||||
|
||||
Avant modification produit, les deux tests ont échoué : témoin après les
|
||||
8/8 logins (~169 ms), et après les 4/4 changements (~84 ms). Réponses correctes
|
||||
mais aucun travail témoin pendant la rafale. Chaque appel bloquait ~21 ms.
|
||||
|
||||
Après modification, l'ancien code a été recopié par `git show 366830f6:<path>`
|
||||
dans une fixture privée sous le `node_modules/.cache` du worktree **source**,
|
||||
avec les tests courants. Les deux mêmes assertions échouent dans chacune des
|
||||
5 exécutions rouges. Aucun checkout ni fichier produit n'a été inversé pour
|
||||
ce contrôle.
|
||||
|
||||
Fixture baseline retenue localement (non versionnée, dans `issue-824-auth`,
|
||||
non recopiée dans le worktree mission) :
|
||||
`node_modules/.cache/auth-baseline-4193325c8f9a4bcd973718259e94c2f6`.
|
||||
Elle contient `auth/{auth-store,manager,password-hash,http-auth,token-manager,session-manager}.ts`,
|
||||
`server/routes/auth.ts` de la baseline et le test de réactivité courant ; les
|
||||
imports de dépendances utilisent seulement le node_modules du worktree source.
|
||||
|
||||
## Mesures exécutées rouge / vert (tâche initiale)
|
||||
|
||||
Windows x64, Node `v25.2.1`, pool libuv par défaut (4), mêmes paramètres scrypt.
|
||||
Cinq processus Node distincts par variante, tests témoins seuls ; pas de seuil
|
||||
de performance ni de conclusion statistique au-delà de ces échantillons.
|
||||
|
||||
| Mesure (médiane des 5 runs) | Baseline | Correctif initial |
|
||||
| --- | ---: | ---: |
|
||||
| Témoin pendant les 8 logins | 168,63 ms | 3,37 ms |
|
||||
| Durée totale des 8 logins | 179,23 ms | 67,33 ms |
|
||||
| Témoin pendant les 4 changements | 82,43 ms | 0,80 ms |
|
||||
| Durée totale des 4 changements | 84,23 ms | 93,57 ms |
|
||||
| État au témoin : logins encore en cours | 0/8 | 8/8 |
|
||||
| État au témoin : changements encore en cours | 0/4 | 4/4 |
|
||||
|
||||
Échantillons témoin, en ms, dans l'ordre des runs :
|
||||
|
||||
- Rouge login : 187,0279 ; 163,5008 ; 161,1307 ; 168,6303 ; 173,3027.
|
||||
- Vert login : 4,6347 ; 3,3659 ; 2,9695 ; 3,1687 ; 3,7206.
|
||||
- Rouge password : 91,9619 ; 82,9719 ; 82,2616 ; 82,4302 ; 82,1526.
|
||||
- Vert password : 1,1573 ; 0,6115 ; 0,6394 ; 0,7956 ; 1,6040.
|
||||
|
||||
La sérialisation des changements vise la cohérence, pas leur débit. Leur temps
|
||||
total n'est pas amélioré ; le témoin et donc la coopération du thread HTTP le
|
||||
sont. Le coût CPU scrypt n'est pas supprimé.
|
||||
|
||||
## Correction et garanties
|
||||
|
||||
- Format v1, sel aléatoire 16 octets, clé 64 octets, paramètres par défaut
|
||||
`N=16384, r=8, p=1, maxmem=32 MiB` inchangés.
|
||||
- Vérification avec les paramètres enregistrés et comparaison
|
||||
`crypto.timingSafeEqual` conservées ; longueur incompatible et algorithme
|
||||
inconnu restent des refus. Les erreurs crypto deviennent des rejets attendus.
|
||||
- Login attend le booléen, sinon une Promise truthy aurait autorisé les mauvais
|
||||
mots de passe. Password attend le calcul **et la persistence** avant succès,
|
||||
et attrape les rejets pour conserver la réponse 409 text/plain.
|
||||
- AuthStore sérialise uniquement les changements de mot de passe par ordre
|
||||
d'admission. Un échec ne bloque pas les suivants ; le bootstrap ne perd pas
|
||||
son identité entre deux calculs ; le dernier changement admis gagne.
|
||||
- Lors de l'intégration, une fence d'identité du record d'autorité a été ajoutée
|
||||
après la dérivation : `valid && this.load() === auth`. Un changement réussi
|
||||
remplace le record cached ; un échec ne le remplace pas. Une dérivation sur
|
||||
un ancien record ne peut plus accorder de session après son remplacement.
|
||||
Aucun nouveau calcul ni replay automatique du login.
|
||||
- Désactivation d'auth, override CLI/env et cookies conservent leur sémantique.
|
||||
- Le hachage synchrone de l'override CLI/env reste explicitement nommé
|
||||
`hashPasswordSync`, appelé seulement par `ensureInitialized` au constructeur,
|
||||
avant le service HTTP. Cela évite d'élargir cette correction au cycle de
|
||||
démarrage. Les lectures/petites écritures auth.json synchrones ne changent pas.
|
||||
- Recherche des callers effectuée dans le dépôt : les deux handlers HTTP sont
|
||||
les seuls callers produit de validateLogin/setPassword ; les await ont été
|
||||
ajoutés. Le typecheck serveur valide cette propagation.
|
||||
- Aucun rate limiter explicite n'a été trouvé dans les routes/auth courantes :
|
||||
aucun mécanisme existant n'est retiré, et aucun nouveau n'est introduit ici.
|
||||
|
||||
## Validation et commandes
|
||||
|
||||
```powershell
|
||||
node --import tsx --test packages/server/src/auth/password-hash.test.ts packages/server/src/server/routes/auth-responsiveness.test.ts packages/server/src/server/routes/auth.test.ts
|
||||
npm run typecheck --workspace @neuralnomads/codenomad
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Tâche initiale : 8 tests verts, répétés cinq fois (40/40 exécutions de tests),
|
||||
puis 5 runs verts supplémentaires des seuls tests témoins. Typecheck serveur
|
||||
vert. Diff check sans erreur (avertissements Git habituels LF/CRLF seulement).
|
||||
|
||||
Intégration : les sept chemins cibles étaient sans modifications au départ.
|
||||
Les quatre fichiers produit et les deux tests ont été repris via patch tool ;
|
||||
seuls `auth-store.ts` et le test de réactivité ont été enrichis pour la course
|
||||
de changement de mot de passe. Le rapport source est repris et actualisé ici.
|
||||
Les changements voisins de mission ne sont pas touchés.
|
||||
|
||||
### Course login / changement de mot de passe : rouge puis vert déterministe
|
||||
|
||||
Le test `rejects a pending login verified against a password replaced before
|
||||
verification returns` fait réellement dériver l'ancien mot de passe par
|
||||
`crypto.scrypt`, mais retient son callback. Il enregistre ensuite un nouveau
|
||||
mot de passe par la vraie route `/api/auth/password` et vérifie le nouveau
|
||||
secret, avant de libérer l'ancien callback. Il ne suppose aucune durée ni
|
||||
ordre implicite du scheduler. Le mock est restauré et les requêtes privées
|
||||
sont drainées avant fermeture.
|
||||
|
||||
Sur le correctif initial intégré sans fence : échec attendu, **200 au lieu de
|
||||
401**, après le succès de la mutation password. Après ajout de la fence :
|
||||
**401 Invalid credentials sans Set-Cookie**, nouveau secret valide, ancien
|
||||
secret refusé. L'authentification ne réutilise pas une autorité remplacée.
|
||||
|
||||
La suite intégrée passe **9/9 tests**, dont les 8 tests précédents et ce nouveau
|
||||
cas ; elle a été rejouée trois fois supplémentaires (27/27 verts).
|
||||
Typecheck serveur vert. Exemple de diagnostics au run intégré : témoin
|
||||
login 3,29 ms avec 8 opérations encore en cours ; password 0,52 ms avec 4 en
|
||||
cours. Ces mesures sont descriptives et séparées du tableau A/B initial.
|
||||
|
||||
```powershell
|
||||
node --import tsx --test --test-name-pattern='rejects a pending login' packages/server/src/server/routes/auth-responsiveness.test.ts
|
||||
```
|
||||
|
||||
Commande témoin courte, utilisable sur le correctif ou avec le chemin du test
|
||||
dans la fixture baseline source ci-dessus :
|
||||
|
||||
```powershell
|
||||
node --import tsx --test --test-name-pattern='services a witness' packages/server/src/server/routes/auth-responsiveness.test.ts
|
||||
```
|
||||
|
||||
Couverture : ancien hachage v1, paramètres personnalisés, nouveau hachage lu
|
||||
par scryptSync indépendant, mot de passe et utilisateur invalides, login valide
|
||||
et cookie, refus de password non authentifié, erreurs override/désactivation,
|
||||
bootstrap concurrent, ordre des changements, échec persistence puis reprise,
|
||||
réactivité login/password, autorité remplacée pendant une vérification et
|
||||
compatibilité du test login HTML existant.
|
||||
|
||||
Installation isolée initiale des dépendances par `npm ci --ignore-scripts`
|
||||
(lockfile inchangé). npm signale 49 vulnérabilités préexistantes ; pas de
|
||||
`npm audit fix` ni de mise à jour hors périmètre. Aucune réinstallation lors
|
||||
de l'intégration ; dépendances de mission existantes uniquement.
|
||||
|
||||
## Limites et suite minimale
|
||||
|
||||
- Les requêtes passent par `Fastify.inject`/light-my-request, pas par un socket
|
||||
réseau ni Chromium. Ceci isole le défaut du thread HTTP, sans prétendre
|
||||
reproduire la saturation HTTP Linux→Windows, SSE ou compaction.
|
||||
- Scrypt utilise toujours du CPU/de la mémoire et partage le pool libuv avec
|
||||
d'autres opérations. La saturation du pool et les rafales non bornées ne sont
|
||||
pas qualifiées ici ; une politique d'admission serait un travail séparé.
|
||||
- La fence compare l'autorité cached/override de ce processus, comme le store
|
||||
existant. Elle ne crée pas de surveillance des changements externes du
|
||||
fichier auth.json ni de révocation des sessions déjà accordées.
|
||||
- Aucun serveur utilisateur, daemon partagé, profil, desktop ou base native
|
||||
OpenCode utilisé ; aucun restart, déploiement, commit ou push.
|
||||
- Intégration effectuée ; revue indépendante et validation finale de mission
|
||||
restent nécessaires. Aucune correction logout ou politique rate-limit.
|
||||
|
||||
## Fichiers du patch
|
||||
|
||||
1. `packages/server/src/auth/password-hash.ts`
|
||||
2. `packages/server/src/auth/auth-store.ts`
|
||||
3. `packages/server/src/auth/manager.ts`
|
||||
4. `packages/server/src/server/routes/auth.ts`
|
||||
5. `packages/server/src/auth/password-hash.test.ts` (nouveau)
|
||||
6. `packages/server/src/server/routes/auth-responsiveness.test.ts` (nouveau)
|
||||
7. `dev-docs/issue-824-auth-responsiveness.md` (ce rapport)
|
||||
|
||||
Tous les fichiers source/test modifiés restent sous les seuils AGENTS.
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
# #824 — gate navigateur final unique
|
||||
|
||||
## Verdict terminal
|
||||
|
||||
**Gate non vert : 360 tests, 358 pass, 1 fail, 1 skip, 0 cancelled.**
|
||||
Commande normative exécutée **une seule fois**, exit 1 **naturel**, sans timeout ni
|
||||
force-exit. Zéro dérive des sources. Travail de validation terminé et rapporté,
|
||||
pas acceptation globale. Aucun retry/ciblé ou second fullbrowser après ce rouge.
|
||||
|
||||
L'échec unique est `git-history.test.ts:684`, sur le contrôle final `errors=[]` :
|
||||
un pageerror `ReferenceError: __name is not defined` vient d'un script navigateur
|
||||
anonyme. Ce n'est **pas** l'ancien échec d'identité du focus ni le clic Monaco.
|
||||
Le snapshot opt-in est présent mais sa chronologie est vide. Le cache HTML et le
|
||||
cas MCP précédemment rouges passent ici, sans démontrer leur résolution causale.
|
||||
Le gate historique 356/3/1 et les diagnostics ciblés restent des pièces séparées.
|
||||
|
||||
## Admission fraîche
|
||||
|
||||
Workdir exact : `D:/CodeNomad/.codenomad/worktrees/issue-824-performance`.
|
||||
HEAD `366830f6b4373e6b283cabf7dd04764e87e1cc32`, changements mission non committés.
|
||||
Rapports `issue-824-browser-focus-test-sync.md` et
|
||||
`issue-824-browser-mcp-critical-capture.md` lus, leurs tâches terminées.
|
||||
Contrôle processus Node read-only préalable : aucun runner navigateur concurrent
|
||||
repéré ; résultat et heure conservés dans `process-preflight.json` et
|
||||
`preflight-time.txt`.
|
||||
|
||||
Nouveau manifeste **1 696 fichiers**, tracked + untracked non ignorés : sources
|
||||
produit, tests, scripts, configs et locks ; exclusions docs/Markdown/PDF,
|
||||
dist/public/deps/cache/release/target. Comparaison avec `combined/.../initial.json` :
|
||||
**exactement les trois inputs autorisés de focus-sync** ont changé :
|
||||
|
||||
- `packages/ui/tests/browser/git-history.test.ts` ;
|
||||
- `packages/ui/tests/browser/session-rendering.test.ts` ;
|
||||
- `packages/ui/tests/browser/fixtures/user-html.tsx`.
|
||||
|
||||
Comparaison des hashes de fichiers avec `mcp-critical/.../terminal-manifest.json` :
|
||||
**aucun écart**. Aucun autre changement à déclarer ou autoriser ; hashes produit
|
||||
inchangés depuis la validation combinée. `admission.json` conserve tous les hashes
|
||||
anciens/nouveaux, chemins de référence et décisions. `browser-before.json` est
|
||||
identique à l'admission ; le runner aurait refusé une dérive avant lancement.
|
||||
|
||||
SHA256 nouveau `initial.json` :
|
||||
`da74db30b68829edd40b44c069191cba8b21cc3e5e5d345862eec0cb9a14eb1c`.
|
||||
SHA256 combined initial historique :
|
||||
`16a3d4085dac90c27e892a480ae4c0735eb52782a9add8b0ca6732a640f1a4df`.
|
||||
Le manifeste MCP map-only est un format différent mais les hashes de fichiers
|
||||
correspondent exactement : SHA
|
||||
`4628acfc4df16f8e573da48dab23df3614a7a8a55643466e5e6e8e4f6e80105b`.
|
||||
|
||||
## Exécution normative et confinement
|
||||
|
||||
```powershell
|
||||
$env:CODENOMAD_BROWSER_BOUNDARY_TRACE = '1'
|
||||
npm run test:browser --workspace @codenomad/ui
|
||||
```
|
||||
|
||||
Le script npm inchangé exécute
|
||||
`node --import tsx --test --test-concurrency=1 tests/browser/*.test.ts`.
|
||||
Pas de filtre, de liste privée substituée, de nouveaux délais/assertions/gestes,
|
||||
ni de policy/cleanup du cache Vite. La capture MCP privée n'est **pas** insérée
|
||||
dans ce fullbrowser : ses cinq passes restent ciblées/historiques, distinctes du
|
||||
test MCP normal exécuté ici. Le trace opt-in préparé active uniquement les surfaces
|
||||
focus/cache existantes. Aucun patch produit/test/fixture/script/config/deps/lock.
|
||||
|
||||
Le runner privé invoque la commande npm exacte via `cmd.exe /d /s /c`, depuis le
|
||||
workdir exact, et conserve stdout/stderr sans filtrage dans `browser.log`.
|
||||
Budget 60 minutes, exécution background avec notification de fin, pas de polling
|
||||
répétitif. `started.json` créé en mode exclusif interdit un second lancement.
|
||||
Si timeout, seule l'arborescence PID de ce runner npm privé est arrêtée, jamais le
|
||||
daemon partagé ; timeout/forced se distinguent d'une sortie naturelle.
|
||||
|
||||
Artefacts :
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/issue-824-browser-final-0e81ac45520348328ab5bc06252a2010/`.
|
||||
Runner `validate.cjs` SHA256 :
|
||||
`2d31356e2f67899fc7a1207e145feb411907ff6f4bdc6bffa2842f12619e599d`.
|
||||
Shell background : `sh_0f95dae060013DD7dMlptG7IAp`.
|
||||
|
||||
Aucun install, commit/push/merge, restart/déploiement, suppression de sessions,
|
||||
accès au daemon/base/profil utilisateur, sous-agent ou autre gate. Les seuls edits
|
||||
du validateur pour cette tâche sont ce rapport et les artefacts TEMP privés.
|
||||
|
||||
## Limites invariantes
|
||||
|
||||
Serveur/build/typechecks/A-B antérieurs restent historiques ; pas de nouvelle
|
||||
exécution nécessaire ici puisque produit/scripts/locks sont identiques. Ce gate
|
||||
ne qualifie ni Linux Electron → Windows LAN/OpenCode 2.0.19, ni la sortie naturelle
|
||||
des stores. Un éventuel vert ne prouve pas l'élimination universelle des freezes
|
||||
ni la cause des rouges précédents. Une panne HTML/Monaco/MCP reste rouge sans retry
|
||||
et sans narration d'innocuité supposée.
|
||||
|
||||
Les snapshots sont ceux des tests existants. Si leur évaluation échoue, le log
|
||||
complet doit conserver l'exception et le rapport distinguer `observation-error`
|
||||
de l'échec produit/assertion ; un snapshot absent n'est pas un état vide démontré.
|
||||
La mission et sa livraison terminale restent la décision du coordinateur.
|
||||
|
||||
## Résultat exact et captures
|
||||
|
||||
- Début UTC `2026-10-01T21:27:41.967Z`, fin `2026-10-01T21:47:52.008Z`.
|
||||
- Durée runner **1 210 016 ms**, soit **20 min 10 s** ; durée Node test
|
||||
`1 209 656.4827 ms`. Notification finale reçue, pas statut déduit du dernier test.
|
||||
- Exit 1, signal null, launchError null, timeout false, forcedExit false,
|
||||
naturalExit true. Aucune action d'arrêt du runner nécessaire.
|
||||
- Total 360 = 358 pass + 1 fail + 0 cancelled + 1 skip, 0 todo, 0 suites.
|
||||
- Skip : `Electron BrowserWindow native zoom retains tab seams and matching
|
||||
scrollbars`, opt-in `CODENOMAD_TEST_ELECTRON` absent. Ne pas confondre avec les
|
||||
autres fixtures Electron du corpus qui passent ; pas de qualification Linux/LAN.
|
||||
- Node v25.2.1 Windows x64 ; Chromium par défaut Playwright, version 153.0.8010.12
|
||||
confirmée par les logs HTTP. `NODE_OPTIONS=--enable-source-maps` hérité inchangé.
|
||||
|
||||
### Unique échec — assertion et limite observationnelle distinctes
|
||||
|
||||
Cas : `central diff inserts local lines and revision-qualified history into the
|
||||
real session composer`, durée cas `3316.8763 ms`, déclaration ligne 590.
|
||||
Assertion rouge ligne **684** : expected `[]`, actual :
|
||||
|
||||
```text
|
||||
ReferenceError: __name is not defined
|
||||
at <anonymous>:2:78
|
||||
at <anonymous>:2:1004
|
||||
at <anonymous>:3:7
|
||||
```
|
||||
|
||||
Le cas a atteint le contrôle final après ses assertions de brouillon, références
|
||||
Git, restitution de focus (`:630–632`), sélection de plage, historique de commit,
|
||||
référence du worktree WSL, autorité de session et restauration de draft. Toutes
|
||||
ces assertions ont donc passé **dans cette exécution**, malgré son statut final
|
||||
rouge. Pas de timeout `activeElement`, de détachement/clic Monaco ou de replay.
|
||||
|
||||
Snapshot existant intégral, avant fermeture :
|
||||
|
||||
```text
|
||||
Git focus boundary trace {
|
||||
events: [],
|
||||
active: { tag: 'BODY', id: '', className: 'enable-motion underline-links' },
|
||||
widgets: [],
|
||||
selections: []
|
||||
}
|
||||
```
|
||||
|
||||
**Observation-error distincte** : la collecte opt-in du script d'init n'a pas
|
||||
produit de chronologie exploitable ; `__name` est un binding auxiliaire absent
|
||||
dans le contexte navigateur. Le source de l'instrumentation (`:595–615`) initialise
|
||||
le tableau puis définit un helper nommé `describe` et les listeners. Le mécanisme
|
||||
attendu de transformation/sérialisation d'un helper par tsx/esbuild est cohérent
|
||||
avec cette erreur, mais le JS exact sérialisé n'a pas été capturé : pas de preuve
|
||||
supplémentaire par nouveau test ou expérience de transpilation dans cette tâche.
|
||||
|
||||
Le snapshot lui-même **n'a pas échoué à l'évaluation** : il est enregistré, sans
|
||||
exception page.evaluate dans le résultat final. `events: []` ne démontre donc pas
|
||||
l'absence de gestes/focus : la capture est défaillante. L'état BODY est celui de
|
||||
la fin, après d'autres navigations ; il ne contredit pas l'assertion textarea passée
|
||||
plus tôt. Les widgets absents en fin ne prouvent pas leur détachement pendant clic.
|
||||
La régression observationnelle ne doit être ni attribuée au produit sur hypothèse,
|
||||
ni ignorée pour déclarer ce gate vert.
|
||||
|
||||
### Frontières qui passent dans ce seul gate
|
||||
|
||||
- MCP `reduce, 150% scale` static rest : pass `1619.5347 ms` ; autres trois cas MCP
|
||||
pass également. Test normal, aucune substitution/capture MCP privée insérée.
|
||||
- HTML/cache : `user HTML mode preserves Markdown, pasted disclosures and code
|
||||
source and fences legacy caches` pass `2196.9656 ms`. Aucune exception/snapshot
|
||||
d'échec HTML imprimé, donc pas de nouvelle attribution du timeout ancien.
|
||||
- Les 358 passes restent celles du corpus normal, pas additionnées aux
|
||||
retries ni aux résultats historiques. Les erreurs de bootstrap antérieures
|
||||
restent non attribuées ; aucun claim d'innocuité universelle.
|
||||
|
||||
## Empreintes et livraison terminale
|
||||
|
||||
`browser-before.json` et `browser-after.json` sont strictement identiques à
|
||||
`initial.json`, SHA `da74db30b68829edd40b44c069191cba8b21cc3e5e5d345862eec0cb9a14eb1c`.
|
||||
1 696 fichiers ; `drift: []`, `driftFromInitial: []`, HEAD inchangé.
|
||||
Le runner exécuté conserve son SHA d'admission. L'immuabilité est constatée aux
|
||||
bornes, pas un verrou filesystem pendant le run.
|
||||
|
||||
| Artefact privé | SHA256 |
|
||||
| --- | --- |
|
||||
| `browser.log` brut complet | `f24b7866e58109e029acdda5947caa24ce282f78818ff4ddd0a69b220f845eea` |
|
||||
| `failures.log` extrait intégral du résumé rouge | `7db7e5c1ecfaf693f0540ea5d732df0c1f34435b55465012a5cf7bced8a3b5f9` |
|
||||
| `boundary-output-through-end.log` trace puis suite jusqu'à fin | `fa7ef5a8b580e05cc810ac80af67fce3f18949c5d600e47e4981bdaad3b45b5f` |
|
||||
|
||||
`browser-result.json` conserve commande/statut/heures/durée/exits/manifests ;
|
||||
`summary.json` vérifie compteurs et hashes ; `git-focus-snapshot.log` conserve le
|
||||
snapshot exact, `boundary-observation-status.json` distingue l'observation-error
|
||||
du contrôle `errors=[]` rouge et du snapshot évalué avec succès. Le postprocesseur
|
||||
privé `summarize.cjs` ne lance aucun test/browser et ne filtre pas le log brut.
|
||||
|
||||
Serveur/build/typechecks/A-B ne sont pas rejoués ; leurs preuves historiques sur
|
||||
produit identique restent qualifiées avec leurs limites déjà rapportées. Aucun
|
||||
nouveau gate, correction, cleanup ou déploiement ajouté pour fabriquer du vert.
|
||||
|
||||
## Prochain minimum utile — décision coordinateur
|
||||
|
||||
Remettre ce **gate rouge unique** et les captures au propriétaire de focus-sync :
|
||||
rendre le script opt-in navigateur autosuffisant à la sérialisation (par exemple
|
||||
source JS explicite sans helper Node externe), sans modifier contrat produit,
|
||||
assertions, délais ou gestes. C'est une recommandation bornée, **pas un patch ou
|
||||
une relance autorisée ici**. Le coordinateur décide s'il faut un suivi séparé ou
|
||||
s'il clôture avec cette limite ; aucune nouvelle boucle d'audit/retry du validateur.
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
# Issue 824 — attribution bornée des échecs navigateur focus et cache HTML
|
||||
|
||||
## Verdict
|
||||
|
||||
Diagnostic borné terminé, sans correction produit ni test.
|
||||
|
||||
- **Focus après insertion Git : course de synchronisation du test confirmée par
|
||||
le contrat du produit; défaut produit #824 non démontré.** Le texte est inséré
|
||||
immédiatement, mais la restitution du focus est volontairement programmée dans
|
||||
une tâche `setTimeout(0)`. L'assertion du test ne synchronise pas cette tâche.
|
||||
- **Cache HTML : non reproduit et non attribué.** Le chemin attendu après
|
||||
`literal(false)` est un hit de cache local synchrone, pas une attente du renderer
|
||||
Markdown. Les preuves disponibles ne permettent pas de choisir entre défaut de
|
||||
réactivité ponctuel, cache Vite/optimisation du harness ou scheduling du run
|
||||
complet.
|
||||
- Une panne différente a été reproduite une fois avant la frontière focus : le
|
||||
widget Monaco a été détaché pendant le clic. Elle confirme que cette fixture a
|
||||
une frontière overlay/DOM instable, mais n'explique pas l'assertion
|
||||
`activeElement` originale et ne doit pas être renommée en régression #824.
|
||||
|
||||
Le gate navigateur complet de la validation combinée reste **non vert**. Les
|
||||
succès ciblés présents ne le remplacent pas.
|
||||
|
||||
## Inputs et identité
|
||||
|
||||
Sources du worktree `issue-824-performance`, HEAD `366830f6`, modifications mission
|
||||
non committées. Node `v25.2.1`, Chromium `153.0.8010.12`, rejets non gérés stricts,
|
||||
concurrence de tests 1. Aucun produit, test, fixture, dépendance ou configuration
|
||||
n'a été modifié.
|
||||
|
||||
Artefact privé :
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/issue-824-focus-cache-diagnosis/diagnosis.json`,
|
||||
SHA256 `F9ABE8BEC0A7A4940B9357448A37AC3AAB1A497BC4C8D8267E0495752ADDB5EA`.
|
||||
Il enregistre les quatre répétitions et les SHA256 des huit inputs lus/exécutés.
|
||||
Les quatre hashes test/fixture pris avant les runs sont identiques à ceux de
|
||||
l'artefact final :
|
||||
|
||||
- `git-history.test.ts` : `1F779351772617138C12694D2236FFADCB557BA85E070E64A04C1CBEE7CC049F`;
|
||||
- `fixtures/git-history.tsx` : `6C73B4FC43D1D4C0B32AB52733919751EBA9FB89BA4E905B542D9B8423C1E1EC`;
|
||||
- `session-rendering.test.ts` : `DE5F5AE9E6F70428860FBD9089E7CC8EF3F87965B6DB6E164EDEEE000626B578`;
|
||||
- `fixtures/user-html.tsx` : `904C3CA2B72B4CE7A2272762557C3BD2D3E06FCC7BDDC0310B9D044F5538E660`.
|
||||
|
||||
Les inputs produit effectivement inspectés et fingerprintés sont
|
||||
`monaco-diff-viewer.tsx`, `session-view.tsx`, `prompt-input.tsx` et `markdown.tsx`;
|
||||
leurs hashes complets sont dans l'artefact. Aucune instrumentation source n'a été
|
||||
nécessaire.
|
||||
|
||||
## Focus Git : attribution
|
||||
|
||||
### Frontière exacte
|
||||
|
||||
1. Le bouton overlay empêche le `mousedown` par défaut afin de ne pas voler la
|
||||
sélection Monaco, puis appelle synchroniquement `onRequestInsertContext`.
|
||||
2. `GitDiffView` produit le texte qualifié; `SessionView.handleInsertPreviewComment`
|
||||
appelle `PromptInputApi.insertComment`.
|
||||
3. `insertComment` appelle `insertBlockContent`. Celui-ci fait immédiatement
|
||||
`setPrompt(nextValue)`, puis programme seulement dans `setTimeout(..., 0)` :
|
||||
`textarea.focus()` et `setSelectionRange()`.
|
||||
4. Le test clique, lit deux fois `composer.inputValue()` puis évalue immédiatement
|
||||
`element === document.activeElement`. Les lectures de valeur ne constituent pas
|
||||
une attente contractuelle de la tâche timer.
|
||||
|
||||
Le run complet original a donc démontré exactement ce que ce séquençage autorise :
|
||||
les deux assertions de contenu passent, puis l'identité de focus est encore fausse
|
||||
à l'instant observé. Il n'a démontré ni perte de brouillon, ni mauvaise cible, ni
|
||||
absence durable de restitution du focus.
|
||||
|
||||
### Replays présents et nouveaux
|
||||
|
||||
La validation combinée avait déjà un replay exact vert et une suite voisine verte.
|
||||
Sur quatre nouvelles répétitions bornées :
|
||||
|
||||
- répétitions 1 et 2 : test complet vert;
|
||||
- répétition 3 : n'atteint pas l'assertion; timeout de 30 s dans `insert.click()`.
|
||||
Playwright rapporte que `<html>` intercepte les pointer events, puis que le
|
||||
bouton overlay est détaché pendant le retry;
|
||||
- répétition 4, focus seul : test complet vert.
|
||||
|
||||
Ainsi l'échec `false !== true` original reste non reproduit dans ce suivi, mais sa
|
||||
cause de test est expliquée par une absence de synchronisation observable dans le
|
||||
code. La panne de clic séparée interdit de qualifier la fixture comme parfaitement
|
||||
stable.
|
||||
|
||||
### Disposition minimale
|
||||
|
||||
Ne pas changer le produit sur cette preuve. Si un gate navigateur entièrement vert
|
||||
est exigé, le plus petit ajustement de test est d'attendre causalement que le
|
||||
textarea devienne `document.activeElement`, dans le délai existant, puis conserver
|
||||
l'assertion d'identité. Ne pas ajouter de sleep, force-click ou retry global.
|
||||
|
||||
La panne overlay distincte mérite, seulement si elle récidive au gate de livraison,
|
||||
une trace opt-in de `widgetPosition`, sélection Monaco, bounding box et événements
|
||||
`focusin/focusout`/pointer autour du clic. Elle ne doit pas être masquée par
|
||||
`click({ force: true })`.
|
||||
|
||||
## Cache HTML : attribution
|
||||
|
||||
### Frontière exacte
|
||||
|
||||
Le fixture monte un `Markdown` avec une part dont `renderCache` contient :
|
||||
|
||||
```text
|
||||
mode = 1:escaped:wrap
|
||||
html = <div class="cached-html">old sanitized HTML</div>
|
||||
```
|
||||
|
||||
En mode initial `literal(true)`, le mode résolu est `1:literal:wrap`; ce cache est
|
||||
donc correctement refusé. Après `literal(false)`, avec `escapeRawHtml` toujours
|
||||
vrai, le mode devient exactement `1:escaped:wrap`. L'effet de `Markdown` doit alors
|
||||
prendre `snapshot.part.renderCache`, exécuter `setHtml(localCache.html)` et retourner
|
||||
avant `renderSnapshot()`. L'apparition de `.cached-html` ne dépend donc ni du
|
||||
chargement du module Markdown, ni de Shiki, ni d'une promesse de highlighting.
|
||||
|
||||
Le timeout original signifie que cette transition synchrone n'a pas été observée
|
||||
dans le DOM. Sans trace de la valeur du signal, du nombre d'exécutions de l'effet,
|
||||
de l'identité du nœud `#cache`, des erreurs page/module ou des mutations DOM, il est
|
||||
impossible de distinguer :
|
||||
|
||||
- effet Solid non relancé ou runtime optimisé incohérent dans ce serveur Vite;
|
||||
- fixture/nœud remplacé pendant le run complet;
|
||||
- autre anomalie de scheduling ou de cache du harness.
|
||||
|
||||
Une lenteur ordinaire de rendu Markdown est écartée par le chemin de cache local.
|
||||
Un défaut produit persistant est affaibli par les preuves : replay exact vert,
|
||||
suite `session-rendering` complète verte (46 tests), puis trois nouvelles
|
||||
répétitions cache **3/3 vertes** sur sources identiques.
|
||||
|
||||
### Disposition minimale
|
||||
|
||||
Aucun patch produit justifié. Ne pas demander davantage de retries ciblés. Si le
|
||||
même timeout réapparaît dans un prochain gate complet autorisé, capturer dans le
|
||||
failure artifact, sans modifier assertion ni délai :
|
||||
|
||||
- valeur courante de `literal` et compteur d'exécutions de l'effet;
|
||||
- `#cache.innerHTML`, connectivité/identité du nœud et MutationObserver;
|
||||
- `pageerror`, console, `requestfailed` et URLs des chunks Vite/Solid chargés;
|
||||
- mode/cache key résolus avant et après le setter.
|
||||
|
||||
Cette trace permettra de tester causalement l'hypothèse de cache d'optimisation
|
||||
Vite partagé. Introduire un `cacheDir` privé peut ensuite être évalué par contrôle
|
||||
négatif; le succès isolé seul ne prouverait pas cette cause.
|
||||
|
||||
## Exécutions bornées
|
||||
|
||||
Commande à deux noms exacts exécutée trois fois, puis focus seul une fois :
|
||||
|
||||
```powershell
|
||||
node --unhandled-rejections=strict --import tsx --test --test-concurrency=1 \
|
||||
--test-name-pattern="^(central diff inserts local lines and revision-qualified history into the real session composer|user HTML mode preserves Markdown, pasted disclosures and code source and fences legacy caches)$" \
|
||||
packages/ui/tests/browser/git-history.test.ts \
|
||||
packages/ui/tests/browser/session-rendering.test.ts
|
||||
```
|
||||
|
||||
Résultats : focus 3 passages complets verts et 1 timeout de clic préalable; cache
|
||||
3/3 vert. Durées des quatre processus : environ 24,9 s, 22,8 s, 54,7 s et 15,0 s.
|
||||
Sorties naturelles; la troisième sort avec code 1 sur timeout Playwright. Aucun
|
||||
fullbrowser, stress, installation, daemon/profil/base utilisateur, restart,
|
||||
déploiement, commit, push ou sous-agent.
|
||||
|
||||
## Décision pour le gate de livraison
|
||||
|
||||
Conserver séparément :
|
||||
|
||||
1. le fullbrowser 356/3/1 à exit 1;
|
||||
2. ses retries et suites ciblées vertes;
|
||||
3. ce diagnostic, qui attribue la course focus au test mais laisse le timeout
|
||||
cache non qualifié et révèle une panne overlay distincte.
|
||||
|
||||
Si le critère de livraison exige un fullbrowser vert, appliquer uniquement la
|
||||
synchronisation causale du focus après décision du coordinateur, instrumenter le
|
||||
cache/overlay de manière opt-in au prochain gate complet, puis exécuter **un** gate
|
||||
figé. Ne pas présenter les replays ciblés comme un gate final vert.
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
# Issue 824 — synchronisation du test focus et traces de frontière
|
||||
|
||||
## Résultat
|
||||
|
||||
La seule correction fonctionnelle est dans
|
||||
`packages/ui/tests/browser/git-history.test.ts` : après les deux assertions de
|
||||
contenu, le test attend maintenant causalement que le textarea déjà résolu soit
|
||||
`document.activeElement`, puis conserve l'assertion d'identité existante.
|
||||
|
||||
Cette attente utilise le délai Playwright existant. Aucun sleep, timeout explicite,
|
||||
retry, force-click, changement de sélection ou changement du contrat focus produit
|
||||
n'a été ajouté.
|
||||
|
||||
Le focus corrigé passe **2/2**. Le second run a toutefois reproduit exactement le
|
||||
timeout `.cached-html` indépendant. Le résultat ciblé global est donc un run vert
|
||||
puis un run rouge; il ne constitue pas un gate navigateur vert et ne démontre pas
|
||||
une correction universelle de #824.
|
||||
|
||||
## Diff exact
|
||||
|
||||
### Synchronisation focus
|
||||
|
||||
Après les assertions inchangées du brouillon et du contexte Git :
|
||||
|
||||
```ts
|
||||
const composerElement = (await composer.elementHandle())!
|
||||
await page.waitForFunction(element => element === document.activeElement, composerElement)
|
||||
assert.equal(await composer.evaluate(element => element === document.activeElement), true)
|
||||
```
|
||||
|
||||
Le textarea est capturé après l'insertion. L'attente observe uniquement la
|
||||
restitution asynchrone du focus déjà prévue par `insertBlockContent()`.
|
||||
|
||||
### Instrumentation observationnelle, désactivée par défaut
|
||||
|
||||
`CODENOMAD_BROWSER_BOUNDARY_TRACE=1` active désormais :
|
||||
|
||||
- pour la fixture Git, les événements `focusin`, `focusout`, `pointerdown` et
|
||||
`click`, le retrait éventuel du widget Monaco, l'élément actif final, la
|
||||
connectivité/position des widgets et la sélection Monaco disponible ;
|
||||
- pour la fixture HTML, la valeur `literal`, le DOM/connectivité de `#cache`, les
|
||||
mutations, les points avant/après setter, le `renderCache`, les ressources de
|
||||
module pertinentes, les `pageerror`, consoles et requêtes échouées.
|
||||
|
||||
En mode normal, la fixture HTML expose directement le setter Solid original. Les
|
||||
observers et wrappers de trace ne sont créés que lorsque la variable opt-in ajoute
|
||||
`trace=1`; seul un snapshot en lecture est disponible pour le dump d'échec déjà
|
||||
existant. Assertions, délais et chemin normal restent inchangés.
|
||||
|
||||
Cette instrumentation prépare le prochain gate unique; elle n'a pas été utilisée
|
||||
pour relancer le cas après le timeout, conformément à la limite de deux runs.
|
||||
|
||||
## Validation bornée
|
||||
|
||||
Commande exacte, concurrence 1 et noms exacts, exécutée deux fois :
|
||||
|
||||
```powershell
|
||||
node --unhandled-rejections=strict --import tsx --test --test-concurrency=1 \
|
||||
--test-name-pattern="^(central diff inserts local lines and revision-qualified history into the real session composer|user HTML mode preserves Markdown, pasted disclosures and code source and fences legacy caches)$" \
|
||||
packages/ui/tests/browser/git-history.test.ts \
|
||||
packages/ui/tests/browser/session-rendering.test.ts
|
||||
```
|
||||
|
||||
Résultats :
|
||||
|
||||
1. exit `0`, 2 pass : focus 12,3 s; cache HTML 9,4 s;
|
||||
2. exit `1`, focus pass 12,7 s; cache HTML timeout 38,8 s sur
|
||||
`page.locator("#cache .cached-html").waitFor()`.
|
||||
|
||||
Le dump du second run est celui d'avant instrumentation et indique
|
||||
`state: undefined`; il ne qualifie donc toujours pas la cause cache. Il établit en
|
||||
revanche que le timeout antérieur est reproductible sur les mêmes sources et doit
|
||||
rester une frontière ouverte, séparée de la correction focus.
|
||||
|
||||
Autres contrôles :
|
||||
|
||||
- `npm run typecheck --workspace @codenomad/ui` : exit `0` après instrumentation ;
|
||||
- `git diff --check` sur les trois fichiers de test/fixture : exit `0`, avec
|
||||
avertissements de conversion LF/CRLF seulement ;
|
||||
- aucun fullbrowser relancé.
|
||||
|
||||
## Empreintes
|
||||
|
||||
Artefacts privés :
|
||||
|
||||
- `C:/Users/Admin/AppData/Local/Temp/opencode/issue-824-focus-test-sync/manifest.json`
|
||||
— SHA256 `8440F3A61D0B59C870E1A64F9AAEFF90F68B5969FD5C77510F82AB2C899C543D` ;
|
||||
- `run-1.log` — SHA256
|
||||
`5354F36A00380DC16397A394C09B58FBD25D4AE3D613EF026024986077080E69` ;
|
||||
- `run-2.log` — SHA256
|
||||
`0499BDEFAB8EC34349CD2C63E1D0402CB1324AF98FF6567E984F5A1F6B15E63B`.
|
||||
|
||||
Le manifest contient les SHA256 avant/après de huit inputs. Seuls les trois inputs
|
||||
de test autorisés ont changé :
|
||||
|
||||
- `packages/ui/tests/browser/git-history.test.ts` ;
|
||||
- `packages/ui/tests/browser/session-rendering.test.ts` ;
|
||||
- `packages/ui/tests/browser/fixtures/user-html.tsx`.
|
||||
|
||||
Les quatre inputs produit fingerprintés sont strictement identiques avant/après :
|
||||
`monaco-diff-viewer.tsx`, `session-view.tsx`, `prompt-input.tsx` et `markdown.tsx`.
|
||||
`fixtures/git-history.tsx` est également inchangé.
|
||||
|
||||
Le manifest de la validation combinée et le manifest du diagnostic précédent sont
|
||||
désormais des preuves **historiques**, car `git-history.test.ts` a volontairement
|
||||
changé. Ils ne fingerprintent pas ce nouvel état de test.
|
||||
|
||||
## Limites et prochain gate
|
||||
|
||||
- La panne distincte de clic/détachement du widget Monaco n'a pas récidivé dans ces
|
||||
deux runs et n'est ni corrigée ni masquée.
|
||||
- Le timeout cache a récidivé une fois; aucun patch Markdown/Solid/Vite n'est
|
||||
justifié sans la nouvelle trace opt-in.
|
||||
- Aucun test au-delà des deux noms ciblés n'a été exécuté. Le fullbrowser précédent
|
||||
reste non vert.
|
||||
|
||||
Après la fin des diagnostics encore en file, exécuter un seul gate navigateur sur
|
||||
sources figées avec `CODENOMAD_BROWSER_BOUNDARY_TRACE=1` et conserver l'artefact
|
||||
complet si cache ou overlay échoue. Ne pas ajouter de retry pour fabriquer un gate
|
||||
vert. Toute correction ultérieure du cache ou de Monaco doit partir de cette trace
|
||||
causale et rester hors de cette tâche.
|
||||
|
||||
## Fichiers volumineux touchés
|
||||
|
||||
- `packages/ui/tests/browser/session-rendering.test.ts` : environ 910 lignes, sous
|
||||
le seuil d'alerte test de 1000 lignes mais proche ; aucun refactor de portée n'a
|
||||
été entrepris.
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
# Issue #824 — bounded Chromium HTTP/1 admission qualification
|
||||
|
||||
## Result and scope
|
||||
|
||||
**No global freeze reproduced in these five isolated scenarios.** Chromium
|
||||
Windows can progress in session B while session A's prompt/compact ownership
|
||||
preflight is held. Closing a page or browser context retires the old HTTP/SSE
|
||||
observers before the upstream handler is released. A new page connects and reads
|
||||
B before that release; late completion does not forward the abandoned mutation.
|
||||
No additional product correction follows from this result.
|
||||
|
||||
This is **Windows Chromium → Windows Fastify over private loopback HTTP/1.1**,
|
||||
not Electron Linux → Windows LAN qualification, not a desktop process restart,
|
||||
and not qualification of an OpenCode 2.0.19 runtime. No OpenCode daemon, database,
|
||||
provider, shared profile, production backend or installed desktop is used.
|
||||
|
||||
Added paths only:
|
||||
|
||||
- `packages/ui/tests/browser/http-admission-responsiveness.test.ts`
|
||||
- `packages/ui/tests/browser/fixtures/http-admission-responsiveness.tsx`
|
||||
- this report
|
||||
|
||||
## What is real versus supplied
|
||||
|
||||
- Playwright 1.63.0 launches installed Chromium **153.0.8010.12** on Windows;
|
||||
Node **25.2.1** runs private ephemeral Fastify listeners.
|
||||
- The browser addresses Fastify directly. Vite serves fixture modules on another
|
||||
origin; there is **no Vite/Node proxy** in the business-request/SSE path.
|
||||
- CDP `Network.responseReceived` asserts `http/1.1` for the real SSE response,
|
||||
B response, CodeNomad metadata and control witness. Fastify also records request
|
||||
`httpVersion === "1.1"`. These are not intercepted Playwright responses.
|
||||
- Production `registerEventRoutes`, `EventBus`, `ClientConnectionManager` and
|
||||
browser `serverEvents` provide one multiplexed EventSource subscription per
|
||||
page. Event payloads are fixture-controlled; there is no native event relay or
|
||||
native compaction event sequence in this experiment.
|
||||
- Production `registerInstanceProxyRoutes`, request admission, runtime fetch and
|
||||
`WorktreeDeletionFence` implement admission/cancellation. The official
|
||||
`@opencode/client@2.0.21` Promise SDK runs in browser **and** backend. Real
|
||||
browser `createInstanceFetch` performs classification/background scheduling.
|
||||
- A disposable upstream Fastify service supplies native-shaped HTTP responses,
|
||||
including the session `{ data }` wire envelope. Its A GET handler deliberately
|
||||
remains held even after its HTTP response socket closes. This is an induced
|
||||
preflight wait, **not** real native compaction or provider execution.
|
||||
- A small ownership manager supplies only the `/fixture` identity and invokes a
|
||||
real SDK location read. No discovery, filesystem or actual worktree scan is
|
||||
performed. Shared production primitives are imported rather than copying the
|
||||
server disconnect fixture's complete fake SDK tree.
|
||||
- Draft input/session switching are minimal Solid fixture controls. Their switch
|
||||
performs the real SDK B read. This measures browser event/input/network progress,
|
||||
not rendering of the full CodeNomad transcript, session store or composer.
|
||||
- “Info” is the real CodeNomad `/api/meta` route, not native `server.info()`.
|
||||
`/api/info` is not exposed by the workspace proxy allowlist and was not added.
|
||||
|
||||
## Scenarios and causal assertions
|
||||
|
||||
Four cases cross prompt/compact with page/context close. Each starts exactly one
|
||||
SSE plus one A mutation whose real backend SDK session read is held. Before release,
|
||||
the browser edits a draft, switches/loads B, reads `/api/meta` and reads a control
|
||||
witness. A stays pending throughout those successful operations.
|
||||
|
||||
A **separate fifth case** adds five distinct agent catalogue read intents through
|
||||
the existing `createInstanceFetch` policy. Only **two** reach upstream and remain
|
||||
held; three stay queued. Distinct agent URLs avoid Chromium's same-URL cache
|
||||
coalescing. This is a scheduling stress witness, not a claim that normal UI issues
|
||||
five such reads on any particular cadence. No six-held-foreground-request fanout
|
||||
is used to manufacture an ordinary-workflow freeze.
|
||||
|
||||
Before releasing any held upstream handler, closing the page/context must produce:
|
||||
|
||||
- zero SSE registrations, old downstream mutation responses, backend SDK A
|
||||
observers, native A HTTP responses and (fifth case) catalogue HTTP responses;
|
||||
- one observed A admission abort, restored request-abort listener count and no
|
||||
residual admission/forwarding close listener;
|
||||
- a completed deletion-fence operation; A is held **before** fence admission,
|
||||
so this checks non-reservation, not cancellation of an already admitted write;
|
||||
- successful new-page SSE/B/control reads with no old observer restarted.
|
||||
|
||||
Upstream handler closures deliberately remain alive until the test gate is opened
|
||||
(one handler, or three with catalogues). They are counted separately from HTTP
|
||||
observers: their intentional retention must not be mistaken for an orphaned HTTP
|
||||
watcher. After release the test waits for those continuations to finish and checks
|
||||
zero A forwards, environment writes and connection invalidations, with no new
|
||||
catalogue dispatch from the destroyed page.
|
||||
|
||||
## Executed validation
|
||||
|
||||
Existing dependencies/browser reused without installation. From the mission root:
|
||||
|
||||
```powershell
|
||||
node --unhandled-rejections=strict --import tsx --test packages/ui/tests/browser/http-admission-responsiveness.test.ts
|
||||
node --unhandled-rejections=strict --import tsx --test packages/ui/tests/browser/http-admission-responsiveness.test.ts packages/ui/tests/browser/event-subscriber-isolation.test.ts
|
||||
node --unhandled-rejections=strict --import tsx --test packages/server/src/server/__tests__/instance-proxy-disconnect-diagnostic.test.ts packages/server/src/server/request-admission.test.ts
|
||||
npm run typecheck --workspace @codenomad/ui
|
||||
```
|
||||
|
||||
Standalone: **5/5 pass**, zero skips, natural exit ~4.0 s. After adding explicit
|
||||
held-handler continuation accounting, combined gate: **10/10 pass**, zero skips,
|
||||
natural exit ~5.1 s (five new cases plus five existing subscriber-isolation cases).
|
||||
UI source typecheck passes; that tsconfig excludes browser tests/fixtures, which
|
||||
are instead executed via tsx/Vite/Chromium. Neither a full browser-suite gate nor
|
||||
full desktop/native-runtime validation is claimed.
|
||||
The existing server disconnect/admission cases also pass **15/15**, zero skips,
|
||||
natural exit ~0.8 s, without editing their fixture or product sources. Scoped
|
||||
no-index whitespace checks on the three new files emitted only Git's LF/CRLF
|
||||
warnings, no whitespace diagnostics (no-index diff status 1 for new contents).
|
||||
|
||||
Descriptive measurements from the combined run (not CPU latency requirements):
|
||||
|
||||
| Scenario | Input/B/meta/control progress while held | Close cleanup | Backend peak TCP sockets |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| prompt, page close | 92 ms | 13 ms | 3 |
|
||||
| compact, page close | 79 ms | 16 ms | 3 |
|
||||
| prompt, context close | 68 ms | 5 ms | 3 |
|
||||
| compact, context close | 74 ms | 5 ms | 3 |
|
||||
| compact + background budget, context close | 72 ms | 7 ms | 5 |
|
||||
|
||||
Socket counts include idle/preconnect connections and are **not** asserted as busy
|
||||
connection counts. Page close left one idle pooled TCP socket but no pending HTTP
|
||||
observer; context close left zero in this run. New pages had one SSE and two TCP
|
||||
sockets. Assertions are causal (B succeeds before gate release, cleanup occurs
|
||||
before release); 10/30/60-second harness bounds detect deadlock only, not latency
|
||||
regression thresholds.
|
||||
|
||||
## Remaining Linux → Windows qualification
|
||||
|
||||
The smallest useful next experiment needs an approved isolated Windows backend
|
||||
and native storage/provider, plus the actual Linux Electron build. Bind that private
|
||||
backend on a test interface restricted to the Linux host; never expose or use the
|
||||
shared daemon. Record backend/UI/native/Electron/Chromium versions, exact scheme,
|
||||
proxy/VPN configuration and whether authentication uses the remote-window path.
|
||||
|
||||
Concrete read-only external control from Linux, using the approved private URL:
|
||||
|
||||
```sh
|
||||
curl --http1.1 --max-time 10 -sS -D private-meta.headers \
|
||||
-o private-meta.json "$WINDOWS_PRIVATE_URL/api/meta"
|
||||
```
|
||||
|
||||
Provide the isolated backend's normal authentication if required, without saving
|
||||
credentials in the report. This curl process does not consume the renderer's
|
||||
connection pool; compare its progress with renderer Network timings, not as proof
|
||||
that the renderer is healthy. In Electron collect the owned window's existing
|
||||
automation/CDP Network events (no fixed debugging port), protocol, connection IDs,
|
||||
request initiator, queue/stall/connect/TTFB timings and one SSE subscription count.
|
||||
Start with one real A compaction and B switch/draft/meta reads; count background
|
||||
reads separately. Compare close/reopen page, close/reopen remote window and full
|
||||
isolated Electron restart **without restarting Windows backend/native runtime**.
|
||||
Capture admission abort, pending SDK reads, relay backlog, upstream execution and
|
||||
backend event-loop progress before/after each boundary. Do not replay a timed-out
|
||||
mutation. A genuine native 2.0.19 run must identify its binary/storage/provider;
|
||||
the pinned 2.0.21 SDK is not evidence about that runtime.
|
||||
|
||||
The current harness is intentionally loopback-only and self-terminating; changing
|
||||
`CODENOMAD_BROWSER_PATH` selects a local browser, not a remote backend. A persistent
|
||||
LAN-controllable version requires a separately authorized fixture/launch change.
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
# #824 — diagnostic borné du bootstrap MCP navigateur
|
||||
|
||||
## Verdict
|
||||
|
||||
**Timeout de readiness reproduit une fois sur cinq ; cause non attribuée.**
|
||||
Quatre reprises passent et une échoue avant les assertions MCP, avec le même
|
||||
`page.waitForFunction(window.mcpFixture)` à 30 s que le fullbrowser antérieur.
|
||||
Les cinq processus sortent naturellement ; pas de skip/force-exit/force-click.
|
||||
Le suivi est terminé dans son budget, mais n'accepte ni la mission ni le gate
|
||||
fullbrowser, qui conserve ses 356 pass / 3 fail / 1 skip.
|
||||
|
||||
La capture a une faiblesse explicite : son plafond commun de 4 000 observations
|
||||
est saturé par les requêtes de modules **avant les diagnostics terminaux**. Elle
|
||||
ne permet pas d'exclure une erreur/rejection tardive ni de dire si le module est
|
||||
bloqué avant son corps, dans `applyUiSettings` ou dans `render`. Aucun défaut
|
||||
produit confirmé, aucun correctif produit ou harness proposé comme déjà validé.
|
||||
|
||||
## Périmètre et méthode
|
||||
|
||||
2026-10-01, Windows x64, Node `v25.2.1`, Chromium `153.0.8010.12`, worktree
|
||||
`D:/CodeNomad/.codenomad/worktrees/issue-824-performance`, HEAD `366830f6`.
|
||||
`combined-validation` et `validation-closeout` sont déjà rapportées terminées.
|
||||
|
||||
**Aucun fichier produit, test ou fixture source édité.** Seul ce rapport est créé
|
||||
dans le dépôt. Aucun audit ou fullbrowser supplémentaire, aucune dépendance/config/
|
||||
lock modifiée, installation, commit/push/merge, restart/déploiement, suppression de
|
||||
sessions, accès au daemon/base/profil utilisateur ou sous-agent.
|
||||
|
||||
Artefacts privés :
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/issue-824-mcp-bootstrap-4770677d4da549e98e255c79c7ecdba3/`.
|
||||
|
||||
Le script `diagnose.cjs` copie le texte exact de `mcp-motion.test.ts` en privé,
|
||||
préserve assertions, timeouts, clics et options Chromium/Vite, puis compile cette
|
||||
copie TypeScript avec l'esbuild déjà installé. Seuls les imports Node des packages
|
||||
et la racine Vite sont rendus absolus, puisqu'elle n'est plus dans le dossier du
|
||||
test original. Vite sert toujours les vrais modules/stylesheet et la fixture source.
|
||||
|
||||
Observations ajoutées en mémoire dans cette copie, sans catch qui transforme un
|
||||
échec en succès ni `preventDefault` d'erreur :
|
||||
|
||||
- console, pageerror, request/requestfinished/requestfailed ;
|
||||
- statut, headers/content-type, URL et type des réponses des modules Vite ;
|
||||
- erreurs et unhandledrejection dans un init-script de page ;
|
||||
- marqueurs privés par transform Vite : entrée du corps de fixture, avant/après
|
||||
settings, avant render et après publication de `mcpFixture` ;
|
||||
- tentative de snapshot terminal dans le finally, puis fermeture originale.
|
||||
|
||||
Le cache Vite conserve son emplacement/options ordinaires (`node_modules/.vite`),
|
||||
pas une nouvelle politique de cache. Le plugin d'observation privé et l'observer
|
||||
peuvent toutefois influencer timing et clé d'optimisation : ceci n'est **pas** un
|
||||
A/B causal contrôlé du cache ou du coût de bootstrap. Aucune suite complète
|
||||
concurrente repérée lors du contrôle read-only préalable ; les ciblés indépendants
|
||||
du reviewer ne constituent pas une charge de l'hôte maîtrisée.
|
||||
|
||||
## Résultats exécutés — cinq reprises, pas davantage
|
||||
|
||||
Filtre exact :
|
||||
`^eight connected MCP servers stay static at rest \(reduce, 150% scale\)$`.
|
||||
Chaque processus exécute un seul cas avec rejets stricts et concurrence 1. Le
|
||||
budget total du runner est **84,7 s**, sous dix minutes ; aucune relance après
|
||||
ces cinq reprises et aucune assertion/durée artificiellement assouplie.
|
||||
|
||||
| Reprise | Résultat du cas | Exit | Naturelle | Processus, durée observée |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | pass | 0 | oui | 11,25 s |
|
||||
| 2 | **fail readiness 30 s** | 1 | oui | 41,03 s |
|
||||
| 3 | pass | 0 | oui | 12,00 s |
|
||||
| 4 | pass | 0 | oui | 9,63 s |
|
||||
| 5 | pass | 0 | oui | 9,46 s |
|
||||
|
||||
Le runner global sort exit 1 car une reprise échoue : il ne masque pas le rouge.
|
||||
Ces cinq exécutions d'un même scénario ne sont pas cinq scénarios de couverture
|
||||
nouveaux et ne remplacent pas le gate complet antérieur.
|
||||
|
||||
## Ce que la capture démontre / ne démontre pas
|
||||
|
||||
Dans la reprise rouge :
|
||||
|
||||
- Le document `/mcp-motion` et `/tests/browser/fixtures/mcp-motion.tsx` répondent
|
||||
**200**, respectivement HTML et JavaScript. Le serveur a appliqué la transform.
|
||||
Une absence totale de réponse de ces deux entrées n'explique donc pas ce run.
|
||||
- Le préfixe enregistré comprend **1 645 requêtes**, **1 175 réponses 200** et
|
||||
**1 175 requestfinished**. Les modules optimisés Solid/debug répondent aussi 200.
|
||||
De nombreux modules sont des icônes `lucide-solid/dist/source/icons/*.jsx`,
|
||||
conformément à `optimizeDeps.exclude: ['lucide-solid']` existant.
|
||||
- Les seuls messages console retenus sont `[vite] connecting...` et `connected.`.
|
||||
Aucun pageerror, requestfailed ou statut >=400 n'apparaît **dans ce préfixe**.
|
||||
Ceci ne prouve pas leur absence dans la totalité du run.
|
||||
- Le plafond de 4 000 événements est atteint vers 10,14 s après début du processus
|
||||
de test. Il est aussi atteint dans les quatre passes (8,42–10,93 s), donc sa
|
||||
saturation n'est ni un discriminateur du timeout ni une cause de freeze établie.
|
||||
- Le log Node démontre le timeout après `goto`, sur la readiness, pas une assertion
|
||||
« huit serveurs ready », ni une animation perpétuelle du composant.
|
||||
|
||||
**Limite de capture confirmée** : les snapshots/stages, événements ready et erreurs
|
||||
tardives partagent le même plafond avec le réseau. Ils ont été perdus, y compris
|
||||
le snapshot terminal qui aurait permis de localiser la frontière. Ne pas convertir
|
||||
cette absence d'observations en absence d'erreur produit. La quantité de modules,
|
||||
une collision/rebuild du cache Vite, l'ordonnancement, la charge de l'hôte ou une
|
||||
attente settings/render restent des hypothèses, pas des causes démontrées.
|
||||
|
||||
Le fixture source substitue l'inventaire Shell et le client MCP ; aucune compaction
|
||||
native ou connexion à un vrai MCP/OpenCode n'est testée ici. Rien dans ces traces
|
||||
n'attribue le freeze original #824 à MCP ni à Electron Linux → Windows.
|
||||
|
||||
## Empreintes et artefacts
|
||||
|
||||
Avant/après chaque reprise : **1 696 fichiers** tracked + untracked non ignorés,
|
||||
sources/tests/scripts/configs/lock inclus, hors docs/générés/dist/public/deps/cache.
|
||||
Pas de dérive et mêmes hashes de fichiers que la validation combinée. Le format
|
||||
du manifeste privé (map path->SHA seulement) diffère du manifeste combiné enrichi.
|
||||
`results.json` confirme `originalTestUnchanged: true`, `fixtureUnchanged: true`.
|
||||
|
||||
- `initial.json`, `<n>-before.json`, `<n>-after.json`, `<n>-result.json` : empreintes
|
||||
et statuts. SHA initial `c4c4ff2bbc0c022b27258bda2ba085f79062f0cb982341b3ced09d0cd07adaa9`.
|
||||
- `test-original.ts`, `fixture-original.tsx`, `test-observed.ts` et
|
||||
`test-observed.mjs`, plus `harness.json` : provenance et modifications privées.
|
||||
- `<n>.log`, `<n>-trace.json` : logs/traces complets **jusqu'au plafond**, pas traces
|
||||
complètes de la vie de page. `trace-summary.json` identifie la saturation.
|
||||
- `2-module-prefix.json` : réponses d'entrée et modules optimisés du rouge.
|
||||
- `2.log` SHA256 `0830b1ee6d0b74c4640c434529227ce61193fcfe8ca01f41251bef114e264284`.
|
||||
- `2-trace.json` SHA256 `d414677c96cb7ab5b4e2a79b18eff64fa6bf07539e5e205333c94725157e4332`.
|
||||
- `diagnose.cjs` SHA256 `a6f4d4702d13624cf8c1123819ec0877da83b8381a07addbd7436fa84f195c1b`.
|
||||
|
||||
## Prochain minimum utile — décision du coordinateur, pas une relance ici
|
||||
|
||||
Autoriser un nouveau suivi **seulement si l'attribution reste nécessaire** : séparer
|
||||
les événements critiques dans une réserve indépendante du réseau, conserver toujours
|
||||
un snapshot terminal/stages/rejections, et agréger les réponses modules par URL/status
|
||||
plutôt que laisser leur volume éjecter le diagnostic. Avec ce harness corrigé, capturer
|
||||
une nouvelle occurrence avant toute décision de patch ou expérience causal-cache.
|
||||
Ne pas changer les assertions, les 30 s, les gestes ni la politique produit pour
|
||||
obtenir artificiellement du vert. Aucun patch produit minimal ne peut être recommandé
|
||||
sur les preuves actuellement disponibles.
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
# #824 — capture critique du bootstrap MCP
|
||||
|
||||
## Verdict terminal
|
||||
|
||||
**5/5 reprises ciblées passent naturellement, timeout non reproduit.** La faiblesse
|
||||
du collecteur est corrigée et vérifiée, et les cinq traces critiques sont complètes
|
||||
jusqu'à fermeture, sans aucun overflow. L'attribution du timeout historique reste
|
||||
ouverte : aucune nouvelle occurrence rouge n'a permis d'en localiser la frontière.
|
||||
Travail de capture terminé, pas preuve d'élimination de panne ni gate global vert.
|
||||
Le fullbrowser historique reste 356 pass / 3 fail / 1 skip ; le rouge MCP précédent
|
||||
1/5 reste conservé, distinct de ce suivi. Aucune sixième reprise n'est lancée.
|
||||
|
||||
## Périmètre
|
||||
|
||||
Suivi borné de `browser-mcp-bootstrap-diagnosis`, pas une nouvelle validation
|
||||
complète. Les dépendances `browser-mcp-bootstrap-diagnosis` et
|
||||
`browser-focus-test-sync` sont terminales `completed`, sans exécution restante,
|
||||
avant le lancement. Rapport focus-sync lu : ses deux ciblés sont finis, focus
|
||||
2/2, mais timeout cache HTML indépendant au second run. Ses trois modifications
|
||||
de tests/fixture font désormais partie de la nouvelle identité à fingerprinter.
|
||||
|
||||
Sources exclusivement :
|
||||
`D:/CodeNomad/.codenomad/worktrees/issue-824-performance`.
|
||||
|
||||
Artefacts exclusivement privés :
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/issue-824-mcp-critical-20261001/`.
|
||||
Le diagnostic précédent reste intact dans `issue-824-mcp-bootstrap-4770677d4da549e98e255c79c7ecdba3`.
|
||||
|
||||
Aucune édition produit/test/fixture/deps/config/lock ni politique de cache modifiée.
|
||||
Seul ce rapport est créé dans le dépôt. Aucun install, commit/push/merge, restart,
|
||||
déploiement, suppression de sessions, accès au daemon/base/profil utilisateur ou
|
||||
sous-agent. Contrôle read-only des processus Node avant lancement : aucun ciblé
|
||||
browser, runner de validation ou fullbrowser concurrent repéré.
|
||||
|
||||
## Seule modification expérimentale : le collecteur privé
|
||||
|
||||
Le harness précédent est recopié sans altérer ses imports/racine absolus,
|
||||
assertions, délais 30 s, gestes, options Vite/Chromium ou marqueurs privés de
|
||||
fixture. Il sert toujours les vrais modules et styles depuis le worktree.
|
||||
|
||||
Le réseau est désormais agrégé par événement/URL/status/type, avec compteur,
|
||||
première/dernière heure et content-type pour les réponses. Plafond de 8 000 clés
|
||||
distinctes, compteurs totaux et overflow réseau explicites. Pas de body HTTP lu
|
||||
ni de modification de chargement ou optimisation lucide.
|
||||
|
||||
Réserves indépendantes : erreurs 512, lifecycle/ready 256 et consoles 256. Chaque
|
||||
réserve compte son propre overflow. Les réponses >=400 sont aussi classées comme
|
||||
erreurs ; les requestfailed/pageerror/test-error ne dépendent pas du plafond réseau.
|
||||
Le snapshot terminal a son propre emplacement hors de tous ces plafonds, y compris
|
||||
un résultat `observation-error` explicite si son évaluation ne réussit pas.
|
||||
Les erreurs/rejections de page disposent d'un buffer 512 et d'un overflow explicite,
|
||||
indépendants du réseau ; les cinq marqueurs de fixture restent conservés.
|
||||
|
||||
Le finally capture le snapshot avant fermeture, sans absorber l'échec du test.
|
||||
Les observations peuvent influencer le timing : ce n'est pas un A/B causal du
|
||||
cache, de l'ordonnancement ou du coût d'import. L'emplacement et les options du
|
||||
cache Vite ordinaires sont conservés, pas nettoyés ou isolés artificiellement.
|
||||
|
||||
## Préflight sans navigateur
|
||||
|
||||
`collector-selftest.cjs` passe naturellement : 10 001 réponses synthétiques sur
|
||||
un plafond réseau de deux entrées provoquent 9 999 overflows. Malgré cela,
|
||||
pageerror, réponse 504, ready-before et snapshot terminal restent présents dans
|
||||
leurs réserves ; l'overflow erreur indépendant est également vérifié.
|
||||
`collector-selftest-result.json` conserve les résultats. Syntaxe du runner vérifiée.
|
||||
|
||||
## Budget et qualification
|
||||
|
||||
Filtre unique inchangé :
|
||||
`^eight connected MCP servers stay static at rest \(reduce, 150% scale\)$`.
|
||||
Rejets stricts, concurrence 1, au plus cinq runs / dix minutes, arrêt automatique
|
||||
sur la première occurrence rouge avec snapshot/stages critiques sans overflow.
|
||||
Pas de force-exit, skip, force-click ou assouplissement des assertions.
|
||||
|
||||
Le runner fingerprint avant/après les sources produit, tests, scripts, configs et
|
||||
locks tracked + untracked non ignorés, hors docs/générés/deps/caches. Il conserve
|
||||
les statuts exacts, heures, logs, traces et leurs SHA256 ; une dérive n'est jamais
|
||||
présentée comme un gate immutable. Son texte, collecteur, copie observée et code
|
||||
compilé ont leurs propres hashes dans `harness.json`.
|
||||
|
||||
## Résultats frais et localisation observée
|
||||
|
||||
Runner `sh_0f9561ee5001s5HSuUnJ0dLtTb` terminé, exit 0 naturel ; notification finale
|
||||
reçue. Windows x64, Node v25.2.1, Chromium 153.0.8010.12. Cinq processus, un cas
|
||||
chacun, zéro fail/skip/cancelled. Budget total 54,35 s ; pas d'arrêt sur rouge car
|
||||
aucun rouge dans ce suivi. Ce ne sont pas cinq nouveaux scénarios de couverture.
|
||||
|
||||
| Reprise | Exit naturel | Durée processus | Entrée corps fixture, horloge page | Settings | Render → publication |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | 0 | 12,04 s | 10,323 s | 29,4 ms | 46,0 ms |
|
||||
| 2 | 0 | 10,20 s | 8,619 s | 32,0 ms | 43,5 ms |
|
||||
| 3 | 0 | 10,42 s | 8,819 s | 26,2 ms | 41,8 ms |
|
||||
| 4 | 0 | 10,11 s | 8,637 s | 30,3 ms | 45,0 ms |
|
||||
| 5 | 0 | 10,40 s | 8,880 s | 29,1 ms | 44,9 ms |
|
||||
|
||||
Dans les cinq passes : `fixture-body → settings-before → settings-after →
|
||||
render-before → fixture-published`, puis `ready-after` observé. Snapshot final
|
||||
`mcpFixture: true`, document complete, huit ready dots, 66 caractères au root.
|
||||
La lecture ready prend 87,3–97,7 ms après son lancement ; `goto(domcontentloaded)`
|
||||
dure 8,625–10,329 s sur l'horloge Node. Ne pas soustraire les horloges Node/page.
|
||||
|
||||
**Localisation du coût dans les passes**, pas de la panne : l'essentiel du temps
|
||||
de navigation observé précède l'entrée du corps de fixture. Cette région comprend
|
||||
fetch/transform/parse et évaluation des dépendances/imports, éventuellement leurs
|
||||
attentes ; les traces ne répartissent pas ce coût. Settings et render arrivent
|
||||
ensuite et achèvent en quelques dizaines de millisecondes. Cela ne prouve pas
|
||||
que le timeout rouge antérieur bloquait dans cette même région plutôt que dans
|
||||
settings/render/ready. Aucun profil CPU ni expérience causal-cache exécuté.
|
||||
|
||||
Chaque run conserve **1 837 requêtes, 1 837 réponses toutes 200, 1 837 finished**, soit
|
||||
5 511 clés événement/URL/status/type, au-delà du vieux plafond commun 4 000 sans
|
||||
perdre le snapshot. Zéro requestfailed, pageerror, test-error, erreur/rejection dans
|
||||
les stages ; overflow réseau/erreurs/lifecycle/console/stages tous zéro. Ceci porte
|
||||
sur ces cinq runs jusqu'au finally, pas sur l'ancien rouge ni sur une vie de page
|
||||
illimitée après fermeture.
|
||||
|
||||
Les consoles **ne sont pas exemptes d'erreurs** : un message EventSource MIME
|
||||
`application/json` au lieu de `text/event-stream` apparaît dans chaque passe,
|
||||
avec quatorze warnings Solid « computations created outside a createRoot or render »
|
||||
et deux messages Vite debug. Le MIME est cohérent avec la route fixture existante
|
||||
qui remplace toutes les requêtes `/api/**` par `{}` JSON. Sa présence dans les cinq
|
||||
passes ne démontre ni sa causalité dans le timeout ni son innocuité universelle.
|
||||
Ces consoles et leurs emplacements sont conservés intégralement ; aucun filtre
|
||||
pour fabriquer du vert.
|
||||
|
||||
## Identité fraîche et pièces terminales
|
||||
|
||||
Les **1 696 fichiers** ont les mêmes hashes à l'initial, avant/après chaque run et
|
||||
au terminal ; zéro drift, y compris entre runs. Le SHA256 des manifests privés
|
||||
`initial.json` et `terminal-manifest.json` est
|
||||
`4628acfc4df16f8e573da48dab23df3614a7a8a55643466e5e6e8e4f6e80105b`.
|
||||
Les seuls écarts avec le diagnostic précédent sont les trois fichiers attendus
|
||||
de focus-sync (`git-history.test.ts`, `session-rendering.test.ts`, `user-html.tsx`).
|
||||
Ils ne sont pas édités par ce suivi ; les anciens manifests restent historiques.
|
||||
|
||||
`harness.json` et `terminal-receipt.json` vérifient les SHA256 exécutés inchangés :
|
||||
|
||||
| Input privé | SHA256 |
|
||||
| --- | --- |
|
||||
| `diagnose.cjs` | `4ffab45b45c46e5d09e6d8f0e2b2259fe04cdfb41a02689826119fbba9b583bc` |
|
||||
| `collector.cjs` | `914cfc1b5b8ab02a98b20157a5c1706056f6ab928b37f0f32bee03a8fe4d08b0` |
|
||||
| `test-observed.ts` | `a76ae27a9fa8ed04c1f0536ab58b809208fda7edb90c7b27b9fff012db0f30da` |
|
||||
| `test-observed.mjs` | `cce160f88fbba195c702e3134bc5a1a638936054dbe8148fe6c774357b380ca1` |
|
||||
|
||||
Test source original SHA256
|
||||
`5820fe789d7470a5c6607998fbf7d068ed25df61611e496e4eec9968537669ec`,
|
||||
fixture original SHA256
|
||||
`f883fa1acd7318b10c5a517e4b1da0983fde384d2581e0046c7232312e299824`.
|
||||
Les copies originales et leurs fichiers source sont inchangés.
|
||||
|
||||
`<n>.log`, `<n>-trace.json`, `<n>-result.json` conservent chacun leur hash vérifié ;
|
||||
`results.json`, `trace-summary.json` et `terminal-receipt.json` indexent les cinq
|
||||
runs, phases, réserves/overflows et sorties. `collector-selftest-result.json`
|
||||
conserve le préflight. Aucun artefact antérieur n'est écrasé.
|
||||
|
||||
## Décision minimale suivante
|
||||
|
||||
**Pas de nouvelle boucle de diagnostic ni patch produit justifié ici.** Le
|
||||
coordinateur peut utiliser ces traces complètes et le harness privé exact comme
|
||||
capture MCP du gate unique s'il l'autorise. Sans occurrence rouge instrumentée,
|
||||
l'attribution reste explicitement non qualifiée ; si elle est un critère obligatoire
|
||||
de livraison, c'est un blocage honnête de ce critère, pas un succès déduit des retries.
|
||||
Ni imports/lucide, cache Vite, charge hôte, settings ou render n'est une cause prouvée.
|
||||
Conserver séparément cache HTML ouvert, Linux Electron → Windows LAN/2.0.19 et
|
||||
sortie naturelle stores non qualifiés. La clôture de mission appartient au coordinateur.
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
# Issue 824 — correction de sérialisation de la trace Git
|
||||
|
||||
## Verdict
|
||||
|
||||
Le défaut introduit par l'instrumentation opt-in est corrigé uniquement dans
|
||||
`packages/ui/tests/browser/git-history.test.ts`.
|
||||
|
||||
La cause est démontrée : sous le loader `tsx` réellement installé, le callback
|
||||
TypeScript passé à `page.addInitScript(callback)` devient une fonction dont
|
||||
`toString()` contient :
|
||||
|
||||
```js
|
||||
const describe = __name(target => /* ... */, "describe")
|
||||
```
|
||||
|
||||
Playwright sérialise le corps de la fonction mais pas le helper module-scope
|
||||
`__name`. Une page Chromium minimale reproduit alors exactement
|
||||
`ReferenceError: __name is not defined`, avec le même stack et une trace vide que
|
||||
le gate final.
|
||||
|
||||
Le script est maintenant fourni par `page.addInitScript({ content })` sous forme
|
||||
de source JavaScript explicite et autosuffisante. Aucun helper global n'est injecté
|
||||
pour cacher l'erreur. Une validation Chromium minimale du contenu exact extrait du
|
||||
fichier est verte : aucune erreur page, 200 entrées bornées après saturation, et
|
||||
les observations focus, pointer/click et `widget-removed` restent actives.
|
||||
|
||||
Le gate final précédent demeure **non vert** : 360 total, 358 pass, 1 fail,
|
||||
1 skip, exit 1 naturel. Aucun fullbrowser ni test ciblé Git/session/MCP n'a été
|
||||
relancé dans cette tâche.
|
||||
|
||||
## Changement exact
|
||||
|
||||
Le callback transformable de `addInitScript` est remplacé par la constante
|
||||
`gitFocusBoundaryInitScript`, un `String.raw` contenant une IIFE JavaScript sans
|
||||
types ni closure externe. `describeElement` et `pushTrace` vivent entièrement dans
|
||||
ce contexte navigateur.
|
||||
|
||||
Toutes les observations passent par `pushTrace`, y compris `widget-removed` ; la
|
||||
limite de 200 entrées couvre donc désormais toute la chronologie, et pas seulement
|
||||
les événements focus/pointer.
|
||||
|
||||
Le snapshot final est entouré d'un `try/catch` observationnel. Une évaluation de
|
||||
snapshot indisponible est journalisée comme telle et ne masque plus une assertion
|
||||
déjà en cours ; `page.close()` reste exécuté après ce bloc. Aucun changement aux
|
||||
assertions, à `errors[]`, aux clics, textes, sélections, délais ou à la
|
||||
synchronisation focus précédemment acceptée.
|
||||
|
||||
## Preuve rouge causale
|
||||
|
||||
Artefact privé :
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/issue-824-browser-trace-serialization-fix/red.json`.
|
||||
|
||||
Le fichier `callback-before.ts` recopie exactement le callback remplacé. Il est
|
||||
importé par `node --import tsx`; la fonction obtenue est passée à
|
||||
`page.addInitScript` dans une page `data:` privée.
|
||||
|
||||
Résultat :
|
||||
|
||||
```json
|
||||
{
|
||||
"hasExternalName": true,
|
||||
"errors": ["ReferenceError: __name is not defined ..."],
|
||||
"snapshot": { "trace": [] }
|
||||
}
|
||||
```
|
||||
|
||||
Le champ `serialized` de l'artefact conserve le JavaScript exact, dont
|
||||
`const describe=__name(...)`. SHA256 `red.json` :
|
||||
`1A3271EB26B265784BD84EBF2D4A7546508551BEF04237156CAEF779586C117F`.
|
||||
|
||||
Ce mécanisme est certain, et non une simple corrélation : la transformation exacte,
|
||||
le binding externe absent, l'exception Chromium et l'état vide correspondent tous
|
||||
au gate. L'ajout de `__name` vient de la transformation `tsx` effective ; aucune
|
||||
hypothèse sur Vite, Monaco ou le produit n'est nécessaire.
|
||||
|
||||
## Preuve verte bornée
|
||||
|
||||
`validate.cjs` lit `git-history.test.ts`, extrait le contenu exact du
|
||||
`String.raw`, puis l'injecte avec `{ content }` dans une page Chromium minimale. La
|
||||
page alterne le focus 260 fois, émet `pointerdown` et `click`, puis retire un widget
|
||||
`.git-change-context-widget` après installation du MutationObserver.
|
||||
|
||||
Résultat :
|
||||
|
||||
```json
|
||||
{
|
||||
"hasExternalName": false,
|
||||
"errors": [],
|
||||
"traceLength": 200,
|
||||
"hasFocus": true,
|
||||
"hasPointer": true,
|
||||
"hasWidgetRemoved": true,
|
||||
"lastKinds": ["focusout", "focusin", "pointerdown", "click", "widget-removed"]
|
||||
}
|
||||
```
|
||||
|
||||
Le budget saturé à 200 et l'entrée `widget-removed` finale démontrent à la fois la
|
||||
borne globale et la persistance des listeners/observer après saturation.
|
||||
|
||||
Artefacts :
|
||||
|
||||
- `green.json`, SHA256
|
||||
`D3024DB5252E029100F2C5D99F28A22F5F7AA186B43C254C0F1AC622CB6554F0` ;
|
||||
- `validate.cjs`, SHA256
|
||||
`9AB9E82D41BF8FAE98913DBF2BB9F22C8DA48C61335C47A32B04D7857F336087`.
|
||||
|
||||
Deux premiers lancements du runner privé n'ont pas atteint Chromium : le premier
|
||||
ne résolvait pas `playwright` depuis TEMP, le second contenait des séparateurs de
|
||||
ligne PowerShell littéraux. Ils ont échoué respectivement avec `MODULE_NOT_FOUND`
|
||||
et `SyntaxError`; le runner final résout explicitement les dépendances depuis le
|
||||
worktree. Ces erreurs de préparation ne sont pas comptées comme rouge/vert produit.
|
||||
|
||||
## Contrôles statiques et empreintes
|
||||
|
||||
- `npm run typecheck --workspace @codenomad/ui` : exit 0.
|
||||
- `git diff --check -- packages/ui/tests/browser/git-history.test.ts` : exit 0,
|
||||
avec le seul avertissement LF/CRLF existant.
|
||||
- Aucun fichier produit modifié par cette tâche.
|
||||
|
||||
Manifest privé :
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/issue-824-browser-trace-serialization-fix/manifest.json`,
|
||||
SHA256 `90405BE5AE1A1BF9C328B226AA3634FB7B49B7C5D754A4B23F569193FD551F32`.
|
||||
|
||||
Par rapport au gate final :
|
||||
|
||||
- `git-history.test.ts` :
|
||||
`CD4776C4B035FE2C19A80BC7F562FC916A0B4F3388DAD7F1C0A13F683E18DEE4`
|
||||
→ `DC7C378CC15E54F22F5D7E1A4C485313F0AF9DF536E1544690A33E3B2B2E5264` ;
|
||||
- `session-rendering.test.ts` reste
|
||||
`D50A2932039D3E44FA06BF1E02AA75D108A6C80520CE15724D229A1FEBA4E982` ;
|
||||
- `fixtures/user-html.tsx` reste
|
||||
`3D7616C20EDCFA32A7388E473C877B89089DA76FA482CD2E91C1160F95AAD631`.
|
||||
|
||||
Les quatre empreintes produit suivies restent celles du gate/focus-sync :
|
||||
|
||||
- `monaco-diff-viewer.tsx` : `3EB01E2C6D255E41A92FDCACBF946455B7A87293B19F3389D4FAE5C1381C3142` ;
|
||||
- `session-view.tsx` : `733C02DE156A75ECD0D2F7C6DCD90A25050C0A1FC5D8EFD2F0E975D649C4E9AB` ;
|
||||
- `prompt-input.tsx` : `9C8BE5BE1336A11E3188F94716DC3BAE6B63C1EB96BF20DF1F31913318345328` ;
|
||||
- `markdown.tsx` : `51EE39FE0CC2FF5CF4258221B48919EF41AE9C3592450DF9D3FAF4EF0D2C3456`.
|
||||
|
||||
Ainsi, parmi les trois inputs qui distinguaient le gate final de la validation
|
||||
combinée, seul le script Git opt-in change dans cette tâche. Le manifeste immuable
|
||||
1 696 fichiers du gate reste une preuve historique, pas le manifeste du nouvel
|
||||
état.
|
||||
|
||||
## Limites et prochain pas
|
||||
|
||||
La validation prouve la sérialisation, l'absence de `pageerror`, la borne et
|
||||
l'activité de la capture dans un DOM minimal. Elle ne rejoue pas le composant
|
||||
Monaco, les assertions Git ni le corpus complet, conformément à l'interdiction.
|
||||
|
||||
Le plus petit prochain pas est une décision du coordinateur : conserver le bilan
|
||||
terminal comme non vert avec ce défaut de test causalement corrigé, ou autoriser
|
||||
ultérieurement un unique gate d'acceptation sur une composition figée. Aucun retry,
|
||||
patch produit ou promesse d'élimination universelle de #824 ne découle de cette
|
||||
preuve.
|
||||
|
|
@ -1,261 +0,0 @@
|
|||
# #824 — validation combinée fraîche
|
||||
|
||||
## Verdict terminal
|
||||
|
||||
**Le gate navigateur complet frais n'est pas vert** : 356 pass, 3 fail, 1 skip.
|
||||
Serveur (après replay), typechecks, builds, assertions stores et fixture native
|
||||
passent, sur sources inchangées. A/B frais positif. Le replay des trois cas passe
|
||||
3/3 naturellement ; les huit suites ciblées passent **104/104** naturellement.
|
||||
Le travail de validation et sa clôture sont **terminés**, pas « tous les gates verts ».
|
||||
Aucun verdict
|
||||
« tout vert » ne peut être déduit d'un succès ciblé. Les résultats ci-dessous ont
|
||||
été exécutés ici, et ne recyclent ni les anciens 348 passes navigateur ni les
|
||||
mesures des audits précédents.
|
||||
|
||||
2026-10-01, worktree `issue-824-performance`, HEAD
|
||||
`366830f6b4373e6b283cabf7dd04764e87e1cc32`, modifications mission non committées.
|
||||
Les quatre dépendances de revue sont completed ; la re-review indépendante a clos
|
||||
M1. Aucun produit/test/config/lock/dependency édité par ce validateur ; seul ce
|
||||
rapport et les artefacts privés de validation sont sa livraison. Aucun déploiement,
|
||||
restart utilisateur, commit/push, install ou sous-agent.
|
||||
|
||||
## Identité des sources
|
||||
|
||||
Artefacts sous :
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/issue-824-combined-b91c8b66fcc840e1909170c5eeb846b9/`.
|
||||
|
||||
`initial.json` enregistre **1 696 fichiers** : Git tracked **et** untracked non
|
||||
ignorés, y compris nouveaux tests/modules/scripts, configs et lockfiles. Parmi eux,
|
||||
1 379 chemins dans les sources packages, 43 suites browser et 69 scripts. Hash des
|
||||
octets bruts, pas de normalisation des fins de ligne. Exclusions : Markdown/PDF/docs,
|
||||
dist/public/node_modules/cache/release/target. Les dépendances installées ne sont
|
||||
donc pas fingerprintées ni modifiées ; environnement/exécutable Node enregistré.
|
||||
|
||||
SHA256 initial :
|
||||
`16a3d4085dac90c27e892a480ae4c0735eb52782a9add8b0ca6732a640f1a4df`.
|
||||
Chaque gate publie son manifeste avant/après, son statut, son log et leurs SHA256
|
||||
dans `<gate>-result.json`. Sur les seize gates/tentatives terminés, **aucune dérive**,
|
||||
y compris par rapport au
|
||||
manifeste initial. La comparaison constate l'immutabilité aux bornes ; ce n'est pas
|
||||
un verrou filesystem interdisant une modification transitoire suivie d'un retour.
|
||||
|
||||
## Gates frais terminés
|
||||
|
||||
Windows x64, Node `v25.2.1`. Tous les exits ci-dessous sont enregistrés, pas inférés
|
||||
de l'apparition d'un dernier test vert. Les sous-gates inclus dans une suite complète
|
||||
ne s'additionnent pas à sa couverture.
|
||||
|
||||
| Gate | Résultat | Sortie | Temps |
|
||||
| --- | --- | --- | --- |
|
||||
| serveur complet, premier run | 792 pass / 1 fail / 6 skip, 799 tests | naturelle, exit 1 | 27,8 s |
|
||||
| test Git concerné isolé | 9/9 pass | naturelle, exit 0 | 14,1 s |
|
||||
| serveur complet, replay | **793 pass / 0 fail / 6 skip**, 799 tests | naturelle, exit 0 | 30,8 s |
|
||||
| typecheck UI | pass | naturelle, exit 0 | 8,2 s |
|
||||
| typecheck serveur | pass | naturelle, exit 0 | 4,0 s |
|
||||
| typecheck Electron | pass | naturelle, exit 0 | 1,2 s |
|
||||
| build serveur, incluant UI + plugins pruning/automation | pass | naturelle, exit 0 | 26,0 s |
|
||||
| stores/reducer/admission/pruning | **141/141 pass** | **force-exit**, exit 0 | 7,2 s |
|
||||
| nouveau test store compactage sans force-exit | 11 assertions affichées pass, pas de résumé final | **timeout 30 s**, SIGTERM privé | non qualifiant lifecycle |
|
||||
| event-source-handlers | 4/4 pass | naturelle, exit 0 | 0,23 s |
|
||||
| diff-check tracked | pass, avertissements LF/CRLF seulement | naturelle, exit 0 | 0,04 s |
|
||||
| fixture native complète, CLI explicite 2.0.21 | pass | naturelle, exit 0 | 63,4 s |
|
||||
| navigateur complet | **356 pass / 3 fail / 1 skip**, 360 tests | naturelle, exit 1 | 21 min 38 s |
|
||||
| A/B renderer, trois couples | 30 samples par variante | naturelle, exit 0 | 90,1 s |
|
||||
| trois échecs navigateur, replay par noms exacts | **3/3 pass** | naturelle, exit 0 | 33,1 s |
|
||||
| huit suites navigateur ciblées | **104/104 pass**, zéro skip/cancelled | naturelle, exit 0 | 306,4 s |
|
||||
|
||||
Le premier échec serveur est conservé intégralement dans `server.log` :
|
||||
`git-worktree-config.test.ts`, test « checks a redirected main checkout when opened
|
||||
from an ordinary linked checkout », échoue dans le **afterEach rmSync** ligne 40
|
||||
sur un répertoire privé avec `EPERM`. Ce n'est pas une assertion Git échouée.
|
||||
Les mêmes sources passent ensuite le test isolé et le replay complet. La cause du
|
||||
refus Windows (processus Git/antivirus/autre) n'est pas démontrée ; ne pas effacer ce
|
||||
run ni revendiquer deux suites complètes vertes.
|
||||
|
||||
Les six skips serveur restent les quatre opt-in WSL write (mode 0600, symlink loop,
|
||||
conflit mode-only, directory-before-rename) et les deux permissions/symlink POSIX.
|
||||
Ils ne sont pas couverts sur cet hôte. Le gate store emploie la convention existante
|
||||
`--conditions=browser --test-force-exit`, avec rejets stricts : il qualifie ses
|
||||
assertions, **pas le nettoyage du processus**. La tentative naturelle du fichier
|
||||
nouveau n'a pas résolu cette limite connue de revue. Aucun timer arbitrairement
|
||||
annulé/unrefé, aucun test/produit modifié pour obtenir un exit vert.
|
||||
|
||||
Le build réexécute `npm run build --workspace @neuralnomads/codenomad` : clean du
|
||||
dist généré, Vite UI, copie public, compilation serveur et bundling des deux plugins.
|
||||
Avertissements non bloquants : imports à la fois statiques/dynamiques, chunks >500 kB
|
||||
et données Browserslist anciennes. Aucun rebuild/relaunch de l'application installée.
|
||||
|
||||
## Qualification native privée fraîche
|
||||
|
||||
Commande exacte avec rejets stricts :
|
||||
|
||||
```powershell
|
||||
node --unhandled-rejections=strict scripts/test-session-pruning-native.mjs 'C:/Users/Admin/AppData/Roaming/npm/node_modules/@opencode/cli/bin/opencode.exe'
|
||||
```
|
||||
|
||||
Le script vérifie **2.0.21**, lance son propre enfant `serve`, provider, config,
|
||||
HOME/XDG et DB privés, et se termine naturellement. Il ne découvre/redémarre/arrête
|
||||
pas le daemon utilisateur. Fixture conservée :
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/opencode/codenomad-pruning-native-BRHy0F`.
|
||||
|
||||
`native.log` confirme subscriber abort/return/throw sans invalider la connexion,
|
||||
relay FIFO/slow recipients/shutdown, worktrees et cache, vraies routes proxy
|
||||
prompt/command/Shell/inbox/cancel/Forms/permissions, pièce jointe 5 MiB, counts/search,
|
||||
outline 1 501 messages, cleanup/retry/restore/fork, découverte et présence du plugin
|
||||
bundlé. C'est **un fixture complet**, pas un nombre de tests à ajouter aux 799.
|
||||
Le restart mentionné dans son log ne concerne que son enfant jetable.
|
||||
|
||||
Cela confirme le bundle et les intégrations sur runtime privé Windows, pas une vraie
|
||||
charge utilisateur/provider, ni Electron Linux → Windows LAN/OpenCode 2.0.19.
|
||||
Les deux anciens fixtures complets et la matrice native compaction du coordinateur
|
||||
restent des preuves historiques distinctes, non additionnées aux résultats ici.
|
||||
|
||||
## Navigateur et A/B
|
||||
|
||||
Replay complet lancé avec `npm run test:browser --workspace @codenomad/ui`, budget
|
||||
60 minutes, sans force-exit/force-click. Avant lancement : demande au coordinateur,
|
||||
journal ancien non trouvé à son chemin exact, puis vérification read-only des
|
||||
processus Node : aucune suite `tests/browser|test:browser` encore en cours. Aucun
|
||||
processus partagé arrêté. Ce replay utilise les sources courantes, pas le vieux gate.
|
||||
|
||||
Le run complet termine naturellement (exit 1), aucun cancelled, aucun drift. Le
|
||||
skip reste « Electron BrowserWindow native zoom retains tab seams and matching
|
||||
scrollbars » : opt-in `CODENOMAD_TEST_ELECTRON` absent (`tab-chrome.test.ts:116`).
|
||||
|
||||
Échecs exacts conservés dans `browser.log` :
|
||||
|
||||
1. `git-history.test.ts:590`, « central diff inserts local lines and revision-qualified
|
||||
history into the real session composer » : assertion ligne 609 `false !== true`
|
||||
sur l'identité textarea/`document.activeElement` après clic Ajouter au prompt.
|
||||
Les deux assertions de texte/référence précédentes passent ; pas de perte de
|
||||
brouillon démontrée par cet échec.
|
||||
2. `mcp-motion.test.ts:47`, « eight connected MCP servers stay static at rest
|
||||
(reduce, 150% scale) » : timeout 30 s sur `window.mcpFixture` avant assertion
|
||||
de géométrie/animation. Cause du non-montage non démontrée.
|
||||
3. `session-rendering.test.ts:82`, « user HTML mode preserves Markdown, pasted
|
||||
disclosures and code source and fences legacy caches » : timeout 30 s ligne
|
||||
104 sur visibilité `#cache .cached-html` après `fixture.literal(false)`.
|
||||
|
||||
Les gates compactage/SSE/HTTP/membership et le sample instrumenté sont verts dans
|
||||
ce même run, mais une régression session-rendering voisine ne l'est pas. Les scopes
|
||||
ne s'additionnent pas aux 360 tests. Le replay ciblé n'altère ni code, timing bounds,
|
||||
clics, exits ou assertions et doit être rapporté séparément.
|
||||
|
||||
### Disposition des trois échecs au replay
|
||||
|
||||
`browser-failures.log` et `browser-failures-result.json` : 3 tests, 3 pass, zéro
|
||||
fail/skip/cancelled, rejets stricts, exit 0 naturel en 33,1 s. Manifeste avant/après
|
||||
identique à l'initial. Filtrage des noms exacts dans les trois mêmes fichiers,
|
||||
**sans modifier leurs fixtures, délais, clics ou assertions**. Ce constat suffit
|
||||
pour dire « non reproduits dans ce replay », pas « résolus » ni « causes connues ».
|
||||
|
||||
| Échec complet | Démontré | Pas démontré / prochain élément minimal |
|
||||
| --- | --- | --- |
|
||||
| insertion Git, focus | contenu et référence insérés ; focus non revenu au textarea à l'instant de l'assertion ; replay pass | Ordre/timing exact de focus non capturé. En cas de récurrence, enregistrer activeElement et les callbacks de restitution de focus autour du clic, sans changer l'assertion. |
|
||||
| MCP readiness | `window.mcpFixture` absent après 30 s dans le complet ; replay pass | Cause d'échec de bootstrap non capturée. Capturer pageerror/console/requestfailed et réponses modules Vite au montage, avant d'attribuer au cache ou au produit. |
|
||||
| user HTML/cache | `#cache .cached-html` pas visible après passage literal(false) ; replay pass | DOM/cache/promise de rendu à cette frontière non capturés. Capturer DOM, état literal et résolution du rendu avant/après toggle au prochain échec. |
|
||||
|
||||
Scheduling/charge de l'hôte, optimisation Vite ou état de cache sont des pistes,
|
||||
pas des causes démontrées. Aucun patch proposé pour fabriquer un gate vert. Une
|
||||
nouvelle suite complète éventuelle relève de la décision du coordinateur après
|
||||
attribution ; le suivi de clôture ne lance aucune suite supplémentaire.
|
||||
|
||||
### Suites ciblées existantes récupérées à la clôture
|
||||
|
||||
Le runner déjà lancé `validate.cjs browser-failures focused` (shell
|
||||
`sh_0f93a730b001Ofu9KyF5KkL76a`, PID 41540 lors du contrôle) est terminé ; son
|
||||
résultat final a été reçu, pas inféré du dernier test affiché. Aucun nouveau run
|
||||
de validation ou audit n'a été lancé lors de `validation-closeout`.
|
||||
|
||||
`focused-result.json` : exit 0 naturel, rejets stricts, 306,4 s, sources avant/après
|
||||
et par rapport à l'initial inchangées. `focused.log` : **104 tests, 104 pass, 0 fail,
|
||||
0 skip, 0 cancelled**. Détail non additif : compactage 5, isolation SSE 5, HTTP/1
|
||||
admission 5, render-cost 13, history-navigation 21, session-rendering 46, task-copy 6,
|
||||
tool-images 3. Le cas user HTML passe donc aussi dans sa suite voisine complète,
|
||||
pas seulement sous le filtre des trois retries. Sample 80 shells sans remount et
|
||||
compteurs/contenus compactage exacts restent vérifiés. Ni ce gate, ni les trois
|
||||
retries ne changent le statut du fullbrowser à exit 1.
|
||||
|
||||
## Pièces terminales et décision suivante
|
||||
|
||||
`terminal-gates.json` indexe les seize statuts, commandes exactes, heures, sorties
|
||||
naturelles/forcées, SHA256 des logs/manifests et dérives. Le runner privé final est
|
||||
conservé sous `validate.cjs`, SHA256
|
||||
`66d119cfe89bd824e3568e5a0447106e06fc5633c2cb0456a86515d9caecb064`.
|
||||
|
||||
Principaux logs SHA256, dans le répertoire d'artefacts ci-dessus :
|
||||
|
||||
| Fichier | SHA256 |
|
||||
| --- | --- |
|
||||
| `browser.log` | `19099de5e3c3ec694334c4667575715d8dc2dc906f80e558cf54773ea0880a1b` |
|
||||
| `browser-failures.log` | `6035e5c53b01dc30cc936d6183d8625ebda890908c5ffe7b24d63e9c8010f130` |
|
||||
| `focused.log` | `bac7cadaf82a5cbf238851a1f5f86eac1b6b431352c0dc5e3161e9db75752628` |
|
||||
| `server.log` (premier échec) | `d8660dcba0935f9a94fdb965eb9a7723a0f9d8000db18e6ea79bbcce96c55247` |
|
||||
| `server-rerun.log` | `8fafb4e813b85cfd8abc74bd7e1e1f6f4b4a9df38bb4a6f7934d9781923f84de` |
|
||||
| `native.log` | `e74886aee42300ee4543a6f2518bc8b714aeec4922f428f4f8ea113efa994ee7` |
|
||||
| `ab.log` | `0d2e7cb864ff89060d552a4aafa3ed0d1da7f1ba2ca17c7fa7068b1d32be9205` |
|
||||
|
||||
Le suivi propriétaire a récupéré ces artefacts, finalisé seulement ce rapport et
|
||||
son index privé, sans edit produit/tests/config/deps, installation, merge, commit,
|
||||
push, suppression de sessions, restart ou déploiement. Les travaux de validation
|
||||
peuvent être rapportés `completed` ; **la qualification globale reste non verte**.
|
||||
Le coordinateur décide de la clôture de mission, pas ce validateur.
|
||||
|
||||
Prochain minimum utile : conserver les trois preuves d'échec global et les retries
|
||||
séparés ; si un fullbrowser vert est un critère de livraison, autoriser un suivi
|
||||
instrumenté des trois frontières décrites plus haut, puis décider d'un nouveau
|
||||
gate complet figé. Ne pas élargir les corrections #824 sur simple hypothèse de
|
||||
flake. La sortie naturelle stores et Linux Electron → Windows LAN/2.0.19 demeurent
|
||||
des limites explicites, pas des gates implicitement acquis.
|
||||
|
||||
### A/B CPU du postimage M1 actuel
|
||||
|
||||
`node scripts/test-render-cost-audit.mjs 3` passe après le fullbrowser : trois
|
||||
couples alternés baseline/fixed, 80 outils × 8 000 caractères, trois warmups puis
|
||||
dix deltas chacun, **instrumentation off**. SHA produit testé
|
||||
`f07ce171a6b1d3394ce94be496b0abd67f8bc0bb48e946eb9173a681c844f401`.
|
||||
Baseline `47f9e43e` a le même blob renderer que `366830f6` (preuve précédente).
|
||||
|
||||
| ms/delta, médiane [min–max], écart-type échantillon | Baseline | Postimage courant |
|
||||
| --- | --- | --- |
|
||||
| main-thread TaskDuration | 324,57 [168,06–743,53], 173,67 | 16,94 [9,93–50,33], 11,27 |
|
||||
| ScriptDuration | 14,51 [7,88–46,20], 9,03 | 0,25 [0,11–32,37], 9,34 |
|
||||
| dispatcher sync | 12,05 [6,70–19,90], 3,70 | 0,20 [0,10–0,50], 0,08 |
|
||||
| layout | 2,88 [1,51–4,27], 0,55 | 0,08 [0,07–0,23], 0,04 |
|
||||
| recalcul style | 10,20 [5,74–13,91], 1,82 | 0,13 [0,10–0,30], 0,05 |
|
||||
|
||||
30 échantillons par variante. CDP cumule jusqu'à deux animation frames ; ce n'est
|
||||
pas une mesure de latence-frame, FPS, heap, débit ou Electron production. Host
|
||||
activity non contrôlée ; pas de seuil absolu CI. Ces chiffres sont nouveaux et
|
||||
séparés des A/B antérieurs au correctif membership. Le fullbrowser instrumenté
|
||||
indépendant compte **80 clones / 0 added / 0 removed** dans chacun de dix deltas,
|
||||
avec assertion anti-remount ; les valeurs zéro du run instrumentation off ne sont
|
||||
pas des compteurs de travail réel.
|
||||
|
||||
Artefact A/B :
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/codenomad-render-cost-srSQOf/metrics.json`,
|
||||
SHA256 `c72a491eba14d5e71178ba2895a7304ce07f98a02d340a53ef9f75f4424b7cd0`,
|
||||
six logs bruts adjacents. `generated-and-ab-hashes.json` identifie aussi les deux
|
||||
plugins bundlés utilisés par la reprise native.
|
||||
|
||||
## Limites et tailles signalées
|
||||
|
||||
- Pas de qualification Electron Linux, LAN/VPN ou runtime 2.0.19, ni preuve
|
||||
d'élimination universelle du freeze signalé. Le test HTTP/1 Chromium est Windows
|
||||
loopback ; native et browser constituent des périmètres distincts.
|
||||
- Buffer compactage sans plafond explicite octets/événements dans une tâche sans
|
||||
yield ; le délai 250 ms n'est pas une garantie de mémoire bornée.
|
||||
- Scan task structurel à chaque révision conserve les bornes 10 000/200 mais peut
|
||||
coûter du CPU même sans changement de membership. Le sample 80 est borné, pas
|
||||
une mesure du pire cas ni de heap/FPS/production desktop.
|
||||
- Un pas SQLite reste synchrone malgré le budget coopératif du scanner outline.
|
||||
- Le hashing async utilise toujours le pool libuv et le CPU/mémoire disponibles.
|
||||
|
||||
Surfaces modifiées par la mission au-dessus du seuil de signalement (~500 lignes),
|
||||
sans refactor de taille par cette validation :
|
||||
|
||||
- `packages/server/src/server/http-server.ts` ~2 346 lignes ;
|
||||
- `packages/server/src/workspaces/manager.ts` ~1 240 ;
|
||||
- `packages/ui/src/stores/instances.ts` ~2 093 ;
|
||||
- `packages/ui/src/stores/opencode-data.ts` ~828 ;
|
||||
- `packages/ui/src/components/tool-call/renderers/task.tsx` ~569.
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
# Issue #824: compaction delta reduction correction
|
||||
|
||||
## Outcome and scope
|
||||
|
||||
Native compaction fragments now aggregate **before** the OpenCode Solid reducer,
|
||||
not merely before the visible transcript projection. No server, transport,
|
||||
`task.tsx`, daemon, user profile or desktop deployment changes belong to this
|
||||
correction. The preceding large synchronous backlog is diagnostic evidence,
|
||||
not an acceptance gate or proof of real provider delivery frequency.
|
||||
|
||||
The implementation follows installed `@opencode/client@2.0.21` declarations and
|
||||
its actual Solid reducer: `session.compaction.delta.data.text` appends to the
|
||||
running row's `summary`; `ended.data.text` replaces it authoritatively; `failed`
|
||||
replaces the running row with an error record. Durable start/end metadata is not
|
||||
changed or synthesized.
|
||||
|
||||
## Product changes
|
||||
|
||||
- `stores/compaction-delta-buffer.ts`: one plain accumulator and one 250 ms timer
|
||||
per instance/session. It uses the exact native delta type and joins chunks in
|
||||
arrival order. The interval begins on the first chunk, so later chunks cannot
|
||||
postpone publication indefinitely.
|
||||
- `stores/opencode-data.ts`: incoming fragments immediately advance CodeNomad
|
||||
message/full-data revisions and fence SDK reads, but emit one aggregate to
|
||||
the reducer per interval. Aggregate reentry does not advance admission twice.
|
||||
Same-session non-delta events flush preceding chunks to retain native order;
|
||||
unrelated sessions/instances cannot force a flush. End/failure, delete,
|
||||
committed revert, pruning invalidation, disposal and reconnect cancel pending
|
||||
content where its authority has ended. Idle flushes before reducer retirement.
|
||||
- SDK history reads flush pending chunks before establishing read authority and
|
||||
before accepting responses. Otherwise a native page fetched after admission
|
||||
could already contain buffered text that would be appended a second time.
|
||||
A stale pre-delta page is rejected and receives the existing trailing read.
|
||||
- Inactive sessions with neither a resident transcript nor a native observer
|
||||
do not materialize compaction payloads. Only their scalar admission fences and
|
||||
an unfinished-compaction marker remain. Final activation uses the native page.
|
||||
Activation during an unfinished skipped start recovers the running row via the
|
||||
existing authoritative resync path, rather than inventing a start/summary.
|
||||
The SDK's 20-row resync preserves the already hydrated native 200-row window.
|
||||
Subsequent streaming resumes interval reduction without duplication.
|
||||
- `stores/instances.ts`: remove the old post-reducer timer. Deferred aggregate
|
||||
application projects once immediately; inactive loaded views remain invalidated
|
||||
but are not hydrated. Client replacement/removal destroys the owned native
|
||||
projection, including its pending buffer, rather than cancelling only the
|
||||
now-removed visible projection timer.
|
||||
|
||||
## Deterministic red/green evidence
|
||||
|
||||
The browser fixture instruments the real native emission and visible projection
|
||||
boundaries through a test-only Vite transform. There is no product telemetry API,
|
||||
mock reducer or render reimplementation. Both the full stylesheet and real
|
||||
`SessionView` are mounted. HTTP/client responses are private fakes, and browser
|
||||
requests outside loopback are blocked.
|
||||
|
||||
| Contract | Baseline `366830f6` | Corrected |
|
||||
| --- | ---: | ---: |
|
||||
| 128 tiny active chunks, immediate reducer emissions | 128 | 0 |
|
||||
| Same burst, after first interval | 128 native emissions | 1 emission + 1 visible projection |
|
||||
| 8 chunks spaced by 10 ms, first interval | 8 emissions | 1 emission + 1 projection |
|
||||
| Second spaced interval | another 8 emissions | another 1 emission + 1 projection |
|
||||
| 128 chunks for never-loaded inactive compaction | 128 emissions | 0 emissions, 0 projections, 0 message-list reads |
|
||||
| Running compaction activated midway, subsequent 2 chunks | 2 emissions | 1 emission + 1 projection, exact cumulative text |
|
||||
| Loaded but now inactive session, 4 chunks | 4 emissions | 1 emission, 0 hidden-view projections |
|
||||
|
||||
All five baseline browser assertions fail by exact counts, not timing or timeout.
|
||||
All five corrected cases pass, including exact authoritative final text,
|
||||
remount, immediate per-arrival revisions, no duplicate chunks, and active-session
|
||||
isolation. Total active burst text is only 530 characters, not the preceding
|
||||
32 MiB corpus. Spaced delivery uses genuine distinct task/timer intervals through
|
||||
the native dispatcher, not one giant callback.
|
||||
|
||||
Descriptive ingress sample: baseline 128 chunks took **1.6 ms**, corrected
|
||||
**0.4-0.5 ms** on the same host with other mission activity. These are sampled
|
||||
development-build dispatcher durations, not controlled CPU benchmarks, frame
|
||||
latency, or a claim of eliminating every freeze. No Long Task threshold remains.
|
||||
|
||||
## Executed gates
|
||||
|
||||
```powershell
|
||||
# Current browser regression (5 pass)
|
||||
node --import tsx --test --test-concurrency=1 packages/ui/tests/browser/compaction-responsiveness.test.ts
|
||||
|
||||
# Neighboring real-renderer/window/navigation regressions (67 pass)
|
||||
node --import tsx --test --test-concurrency=1 packages/ui/tests/browser/history-navigation.test.ts packages/ui/tests/browser/session-rendering.test.ts
|
||||
|
||||
# Native reducer/admission/pruning gates (141 pass, including 11 new compaction cases)
|
||||
node --conditions=browser --import tsx --test --test-force-exit packages/ui/src/stores/opencode-compaction.test.ts packages/ui/src/stores/opencode-data.test.ts packages/ui/src/stores/opencode-data-idle.test.ts packages/ui/src/stores/opencode-data-settlement.test.ts packages/ui/src/stores/session-native-events.test.ts packages/ui/src/stores/session-request-authority.test.ts packages/ui/src/stores/session-pruning-pagination.test.ts packages/ui/src/stores/session-pruning-projection.test.ts packages/ui/src/stores/session-pruning-events.test.ts
|
||||
|
||||
npm run typecheck --workspace @codenomad/ui
|
||||
git diff --check
|
||||
```
|
||||
|
||||
All commands above pass. The first combined browser invocation had a 120 s
|
||||
harness timeout after 17 passing cases; it is not counted as complete. Re-running
|
||||
the neighboring suites with a 360 s allowance completed all 67 cases naturally.
|
||||
Unit gates use the repository's browser-condition/force-exit convention because
|
||||
imported store timers persist. Diff check has only ordinary LF/CRLF warnings.
|
||||
|
||||
To reproduce baseline red without changing the checkout, extract the two
|
||||
`366830f6` blobs `packages/ui/src/stores/instances.ts` and
|
||||
`packages/ui/src/stores/opencode-data.ts` into a private temporary directory.
|
||||
Set `CODENOMAD_COMPACTION_BASELINE` to it, then run the five browser cases. Vite
|
||||
substitutes those blobs read-only and applies the same exact counters. Leave the
|
||||
variable unset for ordinary corrected runs. Both old and corrected code use the
|
||||
same dependencies, components and fixtures.
|
||||
|
||||
## Remaining limits / review targets
|
||||
|
||||
- This establishes avoided reactive work under modest separate event delivery;
|
||||
it does not qualify real EventSource wire traffic or Electron/Linux against a
|
||||
Windows backend. The companion transport/native diagnostics remain distinct.
|
||||
- Same-session non-delta events and native reads intentionally force a flush.
|
||||
A workload continuously interleaving those boundaries can reduce the coalescing
|
||||
ratio; ordering and read authority win over a universal four-flushes/s promise.
|
||||
- Mid-compaction activation uses the existing quiet/revision-stable native resync
|
||||
policy. Sustained arrivals can delay its authoritative catch-up; the already
|
||||
hydrated visible native page remains displayed. This patch does not replace
|
||||
that broader starvation policy or merge arbitrary in-flight history snapshots.
|
||||
- No user database, daemon, provider calls, native-host timing, heap soak or
|
||||
deployment/restart was used. Source/token loss boundaries are covered by mocked
|
||||
deterministic native records, not an end-to-end real provider compaction run.
|
||||
- File-size warning: touched `stores/opencode-data.ts` is approximately **828**
|
||||
lines and `stores/instances.ts` **2,093** lines. The new narrow buffer module is
|
||||
56 lines; no size-only refactor was attempted.
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
# Issue 824 — revue des barrières d'événements
|
||||
|
||||
## Verdict
|
||||
|
||||
Revue indépendante, bornée au diff depuis `366830f6`, de
|
||||
`packages/ui/src/lib/server-events.ts`,
|
||||
`packages/server/src/workspaces/opencode-service.ts` et de leurs nouveaux tests.
|
||||
|
||||
La correction serveur est sûre pour le modèle de partage réel de
|
||||
`@opencode/client` 2.0.21 : l'arrêt d'un abonné ne détruit plus la connexion
|
||||
logique partagée, tandis qu'une vraie fin ou erreur amont invalide encore la
|
||||
génération qui l'a produite. La correction UI isole correctement une exception
|
||||
synchrone levée directement par un callback et conserve l'ordre FIFO. Aucun
|
||||
blocage fonctionnel n'a été trouvé dans ces deux corrections.
|
||||
|
||||
Il reste toutefois une limite UI confirmée et une faiblesse de diagnostic à ne
|
||||
pas présenter comme entièrement résolues.
|
||||
|
||||
## Constats
|
||||
|
||||
### M1 — une erreur Solid différée au flush échappe encore à l'isolation par abonné
|
||||
|
||||
`ServerEvents.notify()` entoure l'appel direct du callback, mais
|
||||
`dispatchBatch()` entoure tous les callbacks d'un `solidBatch`. Une dérivation
|
||||
Solid déclenchée par un setter peut donc lever seulement lors du flush final de
|
||||
`solidBatch`, après le retour de `notify()`. Cette erreur sort à la ligne du
|
||||
`solidBatch`, puis est capturée par le `catch` englobant de
|
||||
`attachEventSourceHandlers`, qui la journalise à tort comme `Failed to parse
|
||||
event`.
|
||||
|
||||
Reproduction isolée avec la distribution navigateur de Solid : un
|
||||
`createMemo` qui lève après `setSignal()` imprime `after-set`, puis l'exception
|
||||
est capturée autour de `batch()`, pas autour du setter. Le fixture navigateur
|
||||
actuel ne couvre qu'un `throw` direct dans le callback et affirme justement
|
||||
l'absence de `Failed to parse event`; il ne détecterait pas cette variante.
|
||||
|
||||
Impact borné : les callbacks natifs et typés du même lot sont invoqués avant le
|
||||
flush, donc l'événement n'est pas supprimé pour ces abonnés. En revanche, le
|
||||
flush réactif peut être interrompu et le diagnostic accuse le transport. Le
|
||||
plus petit suivi utile est un test navigateur où un abonné écrit un signal dont
|
||||
une dérivation lève, puis une décision explicite sur la frontière de batch
|
||||
(isoler aussi le flush par abonné, ou au minimum séparer le diagnostic de
|
||||
dispatch du parsing).
|
||||
|
||||
### L1 — le diagnostic ajouté est désactivé par défaut
|
||||
|
||||
`log.error()` utilise le logger `debug` de l'espace `sse`; les espaces sont
|
||||
désactivés par défaut. Les tests remplacent directement `getLogger("sse").error`
|
||||
et prouvent que la méthode est appelée, mais pas qu'un utilisateur ou un rapport
|
||||
de diagnostic voit l'échec en configuration normale. De plus, `kind` distingue
|
||||
le type d'événement mais pas le bucket wildcard/typé ni l'abonné fautif.
|
||||
|
||||
La capture est préférable à la rupture du flux et ne constitue pas un bug de
|
||||
réactivité. Si ces exceptions doivent être exploitables sur le terrain, ajouter
|
||||
un canal borné réellement collecté et un identifiant de catégorie d'abonné,
|
||||
sans contenu d'événement ni rejeu.
|
||||
|
||||
### L2 — course abort local / panne amont non déterminisée
|
||||
|
||||
Le choix `if (!signal?.aborted) invalidateConnection(connection)` donne à
|
||||
l'abort local la priorité lorsqu'il est simultané à une erreur de `next()`.
|
||||
Avec `SharedEvents`, l'abort retire l'abonné local; une panne physique peut alors
|
||||
être observée seulement par un autre abonné direct, sans invalider immédiatement
|
||||
`OpenCodeSharedService`.
|
||||
|
||||
Le risque paraît faible et auto-récupérable : la tentative suivante du bridge
|
||||
rouvre le flux partagé; son échec non annulé invalide alors la connexion, et les
|
||||
mutations ordinaires invalident également sur erreur. Ne pas modifier la logique
|
||||
sans reproduction. Ajouter au besoin un test à barrières contrôlées où abort et
|
||||
rejet amont concourent, puis vérifier qu'une reconnexion échouée renouvelle bien
|
||||
le client.
|
||||
|
||||
## Propriétés vérifiées
|
||||
|
||||
- Le SDK 2.0.21 partage un seul flux physique et maintient des files par abonné;
|
||||
`return()`/abort suppriment seulement l'abonné et arrêtent la source uniquement
|
||||
quand il ne reste aucun abonné.
|
||||
- `invalidateAfterStream()` distingue correctement :
|
||||
- erreur/EOF de `iterator.next()` non annulé : invalidation;
|
||||
- `return`, `throw` consommateur ou abort local : nettoyage par
|
||||
`iterator.return()` sans invalidation;
|
||||
- événement d'une ancienne génération : `assertCurrent()` rejette avant le
|
||||
`yield`, et l'invalidation conditionnelle ne peut pas évincer le remplacement.
|
||||
- Le `finally` appelle toujours `iterator.return()`; avec le SDK réel cet appel
|
||||
est résolu sans rejet.
|
||||
- Le dispatch UI continue après un `throw` direct, sans rejeu, pour les abonnés
|
||||
wildcard, typés, open et status; le test FIFO/unsubscribe traverse le vrai
|
||||
EventSource, les routes SSE et le réducteur natif.
|
||||
- Les callbacks enregistrés dans le produit sont synchrones; aucun abonné
|
||||
`async` actuel ne crée de rejet de promesse non capturé à cette frontière.
|
||||
|
||||
## Validation exécutée
|
||||
|
||||
- `node --import tsx --test --test-concurrency=1 packages/ui/tests/browser/event-subscriber-isolation.test.ts` — **4/4 vert**.
|
||||
- `npm exec --yes tsx -- --test packages/server/src/workspaces/subscriber-lifetime.test.ts packages/server/src/workspaces/opencode-connection-generation.test.ts` — **10/10 vert**.
|
||||
- `node --import tsx --test packages/server/src/workspaces/opencode-service.test.ts` — **14/14 vert**.
|
||||
- `node --import tsx --test packages/server/src/workspaces/instance-events.test.ts packages/server/src/workspaces/instance-event-routing.test.ts` — **31/31 vert**.
|
||||
- `npm run typecheck --workspace @neuralnomads/codenomad` — vert.
|
||||
- `npm run typecheck --workspace @codenomad/ui` — vert.
|
||||
- `git diff --check` sur les fichiers revus — vert (seuls avertissements de
|
||||
conversion LF/CRLF du worktree).
|
||||
|
||||
Le lancement global `test:browser` avec un filtre placé après le glob a été
|
||||
interrompu à 120 s car le runner a commencé d'autres fixtures; il ne constitue
|
||||
pas un échec de test. Le fichier navigateur ciblé a ensuite été exécuté seul et
|
||||
est vert.
|
||||
|
||||
## Taille des fichiers touchés
|
||||
|
||||
Aucun fichier produit revu ne dépasse le seuil d'avertissement :
|
||||
`server-events.ts` ~219 lignes et `opencode-service.ts` ~360 lignes. Les nouveaux
|
||||
tests font respectivement ~123 et ~116 lignes.
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
# Issue #824: HTTP preflight cancellation
|
||||
|
||||
## Scope and result
|
||||
|
||||
Baseline: `366830f6`; original red diagnostic retained in commit `1ece148c`.
|
||||
This fixes **orphaned HTTP preflight observers**, not the original global freeze.
|
||||
Initial implementation is in `issue-824-transport`; the seven paths are now
|
||||
integrated uncommitted in `issue-824-performance`. No shared daemon, database,
|
||||
profile, desktop restart or deployment is used.
|
||||
|
||||
When prompt/compact waits on a native ownership read, disconnecting the desktop
|
||||
previously left that read active because disconnect tracking began only after
|
||||
authorization. The original HTTP fixture failed for both operations while an
|
||||
independent session answered in 12/3 ms. This shows a cancellation defect, not
|
||||
proof that native compaction stalls that read in the user's environment.
|
||||
|
||||
## Small correction
|
||||
|
||||
- `server/request-admission.ts` observes request `aborted` and incomplete
|
||||
response `close` from the start of the instance proxy handler. It does not
|
||||
observe ordinary request `close`, which can mean a completely read POST body.
|
||||
- `http-server.ts` propagates the signal to profile negotiation, session,
|
||||
project, PTY/Shell and supported location-ownership native reads. Waiting on
|
||||
shared acquisition or non-cancellable filesystem/inventory work races only
|
||||
this observer against cancellation; it neither cancels nor invalidates the
|
||||
shared service. Late continuations cannot advance admission.
|
||||
- The scope is disposed on every return/error. Mutation fence acquisition and
|
||||
environment/native dispatch have abort guards. The existing 15-second
|
||||
environment deadline and two-second advisory Git-context deadline remain;
|
||||
there is no new timeout on prompts, compaction or execution.
|
||||
- The existing forwarding scope installs its response-close handler before
|
||||
dispatch, without an asynchronous gap; the early scope remains until that
|
||||
handoff settles. `compatibility/proxy.ts` now refuses dispatch when its initial
|
||||
downstream check already found a disconnected response.
|
||||
- No interrupt, replay or daemon cancellation is added. Disconnect after an
|
||||
accepted prompt/compact continues to abandon its HTTP response as before;
|
||||
an upstream operation already admitted can finish independently.
|
||||
- `WorkspaceManager.ownsLocation` accepts an optional signal for its native
|
||||
location check. Existing directory and identity authorization is unchanged.
|
||||
|
||||
## Executed validation
|
||||
|
||||
Windows, Node 25.2.1, private ephemeral loopback listeners. The junction created
|
||||
for the earlier diagnosis was removed with `cmd /c rmdir node_modules` (junction
|
||||
only), followed by a clean `npm ci` in this worktree. The prior missing-dependency
|
||||
typecheck was an installation-state limitation, not a demonstrated source defect.
|
||||
`npm ci` reports 49 dependency vulnerabilities; no out-of-scope upgrade is made.
|
||||
|
||||
Commands:
|
||||
|
||||
```powershell
|
||||
npm ci
|
||||
npm run typecheck --workspace @neuralnomads/codenomad
|
||||
node --unhandled-rejections=strict --import tsx --test packages/server/src/server/__tests__/instance-proxy-disconnect-diagnostic.test.ts packages/server/src/server/request-admission.test.ts packages/server/src/server/__tests__/instance-proxy.test.ts packages/server/src/server/__tests__/instance-proxy-legacy.test.ts packages/server/src/opencode/compatibility/proxy.test.ts
|
||||
$env:TEMP='C:\Users\Admin\AppData\Local\Temp\opencode'
|
||||
$env:TMP=$env:TEMP
|
||||
node --unhandled-rejections=strict --import tsx --test "packages/server/src/**/*.test.ts"
|
||||
git diff --check
|
||||
```
|
||||
|
||||
- Server typecheck passes after clean installation.
|
||||
- Focused run: **65/65 pass**, natural exit. Twelve real-HTTP regressions and
|
||||
three scope tests include actual Promise SDK HTTP cancellation for prompt and
|
||||
compact, independent-session response while a preflight is held, delayed
|
||||
connection/profile/ownership, pre-environment snapshot, environment write,
|
||||
no forwarding after abort, no connection invalidation/replay, admitted
|
||||
operation completion, deletion-fence release and listener disposal.
|
||||
- Timing thresholds were removed from the independent-session assertion: it
|
||||
must finish while the first read is causally held. Descriptive final aggregate
|
||||
latencies are **22/12 ms**, not a performance guarantee.
|
||||
- Entire server gate: **782 tests, 776 pass, 0 fail, 6 declared skips**, natural
|
||||
exit in ~34.7 s. Skips: four opt-in native WSL write tests and two POSIX file
|
||||
permission/symlink tests. This count includes the focused cases, not additional
|
||||
distinct coverage. Full log: `sh_0f8691e64001fwNIml1REbA1tC.out` in the harness's
|
||||
managed shell storage.
|
||||
- No full desktop/UI build or native Linux-to-Windows qualification is claimed.
|
||||
|
||||
## Remaining limits
|
||||
|
||||
Shared service acquisition and shared inventory work may continue after one
|
||||
observer leaves; cancelling those would harm other consumers. The local route
|
||||
retires immediately and refuses any late mutation admission. A buffered request
|
||||
body is parsed before the route handler; this patch does not change parser cost.
|
||||
|
||||
Next: independent review of cancellation/forwarding ownership, followed by
|
||||
integration validation. Separately measure real compaction, native session-read
|
||||
latency and Chromium HTTP/1 connection occupancy using a private runtime. This
|
||||
patch alone does not establish or eliminate the reported application-wide freeze.
|
||||
|
||||
Touched oversized sources: `server/http-server.ts` ~2346 lines,
|
||||
`workspaces/manager.ts` ~1240 lines. No unrelated size-driven refactor is made.
|
||||
|
||||
## Integration validation
|
||||
|
||||
Source: `d9598e42` on `fix/issue-824-transport`, including the original diagnostic
|
||||
from `1ece148c`. Destination:
|
||||
`D:\CodeNomad\.codenomad\worktrees\issue-824-performance` at baseline `366830f6`.
|
||||
All seven paths were applied with the patch tool, without cherry-pick, commit or
|
||||
push. The three modified production files matched the untouched destination
|
||||
baseline before integration. All seven Git blob hashes then matched the source
|
||||
commit exactly; only this report was subsequently extended with integration
|
||||
evidence. No neighboring concurrent changes were rewritten.
|
||||
|
||||
Integrated paths:
|
||||
|
||||
- `packages/server/src/server/http-server.ts`
|
||||
- `packages/server/src/workspaces/manager.ts`
|
||||
- `packages/server/src/opencode/compatibility/proxy.ts`
|
||||
- `packages/server/src/server/request-admission.ts`
|
||||
- `packages/server/src/server/request-admission.test.ts`
|
||||
- `packages/server/src/server/__tests__/instance-proxy-disconnect-diagnostic.test.ts`
|
||||
- `dev-docs/issue-824-http-admission-fix.md`
|
||||
|
||||
The existing clean integration dependencies were reused without installation.
|
||||
Executed from the destination, Windows / Node 25.2.1:
|
||||
|
||||
```powershell
|
||||
node --unhandled-rejections=strict --import tsx --test packages/server/src/server/__tests__/instance-proxy-disconnect-diagnostic.test.ts packages/server/src/server/request-admission.test.ts
|
||||
node --unhandled-rejections=strict --import tsx --test packages/server/src/server/__tests__/instance-proxy-disconnect-diagnostic.test.ts packages/server/src/server/request-admission.test.ts packages/server/src/server/__tests__/instance-proxy.test.ts packages/server/src/server/__tests__/instance-proxy-legacy.test.ts packages/server/src/opencode/compatibility/proxy.test.ts
|
||||
npm run typecheck --workspace @neuralnomads/codenomad
|
||||
git diff --check -- packages/server/src/server/http-server.ts packages/server/src/workspaces/manager.ts packages/server/src/opencode/compatibility/proxy.ts
|
||||
```
|
||||
|
||||
Results: **15/15** scope/disconnect and **65/65** focused pass with natural exits;
|
||||
server typecheck and scoped diff-check pass. The 15 cases are included in the
|
||||
65-case run, not distinct extra coverage. Descriptive witness latencies are
|
||||
12/9 ms in the first run and 10/9 ms in the focused run. No full integration
|
||||
server gate was rerun here; the coordinator's fresh aggregate validation follows
|
||||
the remaining integrations.
|
||||
|
||||
Guard verification: the outer entry checks abort before acquisition begins;
|
||||
`wait()` checks again after each resolved boundary before the caller continues;
|
||||
`clientForRequest()` checks before allocating shared-client work. Explicit guards
|
||||
precede mutation-fence entry, environment write, fetch acquisition handoff and
|
||||
forward dispatch. There is no asynchronous gap between the final forwarding
|
||||
guard and installation of its close listener. Late shared completion is exercised
|
||||
by held connection/profile/ownership/environment tests and cannot advance to a
|
||||
later effect. Work already admitted to the upstream remains outside this local
|
||||
observer's interruption authority.
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
# Issue #824 — private native compaction isolation
|
||||
|
||||
## Result
|
||||
|
||||
**No cross-session service freeze reproduced in this bounded scenario on
|
||||
OpenCode 2.0.21 / Windows.** A genuine OpenCode session compaction waited on a
|
||||
deliberately held loopback summary response. While it was still waiting,
|
||||
session B accepted a prompt, executed its model request and completed
|
||||
successfully. Native session reads, activity and server info remained available.
|
||||
|
||||
The subsequent large-context qualification below sends verified 2/8/32 MiB
|
||||
tool payloads from **A itself** to its compaction provider. It observes transient
|
||||
pre-provider latency (up to ~85 ms in the outline-disabled matrix), but no
|
||||
persistent cross-session freeze. This supersedes only the original size limit,
|
||||
not its separate outline measurements.
|
||||
|
||||
This result separates provider waiting from service starvation; it does not
|
||||
identify or disprove the reporter's root cause. It does not qualify OpenCode
|
||||
2.0.19, Electron Linux, a remote Windows HTTP backend, or compaction work beyond
|
||||
the bounded synthetic sizes below. Only the explicitly supplied 2.0.21 executable was tested; no other
|
||||
runtime was installed or discovered.
|
||||
|
||||
## Fixture and isolation
|
||||
|
||||
Added `scripts/test-compaction-isolation-native.mjs` (383 lines). It follows the
|
||||
existing isolated native-pruning fixture architecture:
|
||||
|
||||
- Requires an absolute CLI path. Runs `--version` and a child
|
||||
`serve --hostname 127.0.0.1 --port 0`, never `service start/restart` or service
|
||||
discovery. Authenticated server metadata must match the created child's PID
|
||||
and executable version.
|
||||
- Gives that child fresh HOME, USERPROFILE, XDG roots, config directory and
|
||||
explicit database, all beneath the approved temporary directory. Project
|
||||
config discovery and model-catalog fetching are disabled.
|
||||
- Uses only synthetic prompts/data and an HTTP loopback provider. Fixture hooks
|
||||
add request-kind/session headers and one inert tool; they do not override
|
||||
compaction results. A real provider request with kind `compaction` is held and
|
||||
then streams the summary through OpenCode's normal completion path.
|
||||
- Builds the current session-pruning source into a temporary plugin bundle,
|
||||
explicitly configured against the private database. This tests outline work
|
||||
in the real native plugin host, not just an in-memory standalone reader.
|
||||
- Retains private `result.json` and `server.log` evidence. Finally aborts its
|
||||
event subscriber, releases fixture gates, stops only its child, awaits its
|
||||
exit and closes the provider connections/server. The script exits naturally.
|
||||
- No product edits, shared daemon/base access, user-profile mutation, commit,
|
||||
deployment or restart occurred.
|
||||
|
||||
Contract sources consulted before fixture adaptation: V2 client/API/plugin
|
||||
guides and the installed `@opencode/client@2.0.21` generated declarations
|
||||
(`session.compact/wait/get/active`, import/export and event timestamps), plus
|
||||
`@opencode/plugin` session HTTP hook declarations. Configuration was checked
|
||||
against the V2 config guide, not the legacy JSON schema.
|
||||
|
||||
## Procedure and causal assertions
|
||||
|
||||
1. Create and warm A/B with real native assistant/tool messages.
|
||||
2. Outside the measured intervals, import a separate outline corpus of
|
||||
128 assistant tool messages × 256 KiB textual output (~32 MiB).
|
||||
3. Submit A's compaction and observe its real provider request and
|
||||
`session.compaction.started`. Keep the response gated.
|
||||
4. Sample native info, A/B get and activity concurrently four times.
|
||||
5. Submit B's prompt and separately gate its primary provider request. Assert
|
||||
native activity reports **both sessions running**. Release B only; await its
|
||||
successful outcome and assistant conclusion while A's wait remains pending
|
||||
and no A compaction-ended event exists.
|
||||
6. Run the real outline RPC against the separate corpus during A's provider
|
||||
wait; concurrently sample native `/api/info` as an external heartbeat.
|
||||
7. Keep A held for another second, verify B get/info again, then release A.
|
||||
Require a completed compaction message in native context and an ended event,
|
||||
with no compaction-failed event.
|
||||
|
||||
The test has request/condition deadlines, but does not assert a fragile
|
||||
millisecond performance threshold. Its main assertion is causal: B completes
|
||||
**before the only mechanism that releases A is invoked**.
|
||||
|
||||
## Original small-context measurements
|
||||
|
||||
Two complete measured runs with the one-second extended hold:
|
||||
|
||||
| Measurement | Run `qAF00E` | Final run `cb4Y79` |
|
||||
| --- | ---: | ---: |
|
||||
| A compaction inbox admission | 17.1 ms | 8.1 ms |
|
||||
| Total interval A held, from compact submission | 1485.7 ms | 1473.4 ms |
|
||||
| Four info reads during hold | 3.0–7.0 ms | 2.0–4.5 ms |
|
||||
| Four A get reads during hold | 3.3–5.4 ms | 3.2–5.7 ms |
|
||||
| Four B get reads during hold | 2.6–4.6 ms | 3.3–5.7 ms |
|
||||
| Four native activity reads during hold | 2.0–4.3 ms | 1.8–5.6 ms |
|
||||
| B prompt admission while A held | 6.3 ms | 6.7 ms |
|
||||
| B completion wait after releasing B, A still held | 21.0 ms | 9.9 ms |
|
||||
| A completion after releasing A | 11.9 ms | 12.7 ms |
|
||||
| Outline RPC, 128 × 256 KiB | 132.0 ms | 96.6 ms |
|
||||
| Concurrent outline native-info requests | 7 | 7 |
|
||||
| Maximum outline info-request latency | 12.4 ms | 10.5 ms |
|
||||
|
||||
In the final run, six info requests completed before the outline finished; a
|
||||
seventh began near its end and completed afterward. These are native HTTP round
|
||||
trips, not a main-thread deadline or renderer frame-rate measurement. The
|
||||
working-source pruning bundle SHA-256 was
|
||||
`3222a741d8707ac672c4b6e0c199993437372fbccabac769c7bbf3821e1a510a`.
|
||||
|
||||
Both runs observed one compaction started, 29 real compaction deltas, and one
|
||||
compaction ended. A fast observer records `received - event.created` using
|
||||
same-host wall clocks: held-compaction/both-held session-event maximums were
|
||||
23 ms; summary-release maximum was 10 ms. No growing delivery backlog was
|
||||
observed in those phases. This is **delivery-lag evidence**, not an inspection
|
||||
of an internal queue, a losslessness guarantee, or absence of all backlog.
|
||||
|
||||
The seed/import phase had maximum event-delivery lag of 264/193 ms respectively.
|
||||
It is kept separate: bulk import, local JSON encoding/GC and native work can
|
||||
contribute, and this fixture does not attribute that delay to compaction.
|
||||
|
||||
## Commands and retained evidence
|
||||
|
||||
```powershell
|
||||
$env:TEMP='C:\Users\Admin\AppData\Local\Temp\opencode'
|
||||
$env:TMP=$env:TEMP
|
||||
node scripts/test-compaction-isolation-native.mjs `
|
||||
'C:\Users\Admin\AppData\Roaming\npm\node_modules\@opencode\cli\bin\opencode.exe'
|
||||
node --check scripts/test-compaction-isolation-native.mjs
|
||||
```
|
||||
|
||||
Final run: PASS, OpenCode 2.0.21, child PID 19312, private endpoint
|
||||
`http://127.0.0.1:63236` (stopped afterward).
|
||||
|
||||
Evidence directories beneath
|
||||
`C:\Users\Admin\AppData\Local\Temp\opencode\opencode\`:
|
||||
|
||||
- `compaction-isolation-qAF00E/result.json` and `server.log`
|
||||
- `compaction-isolation-cb4Y79/result.json` and `server.log`
|
||||
|
||||
An earlier successful development run used a shorter hold. The first setup
|
||||
attempt failed before creating a server because `--version` had already made
|
||||
the private config directory; directory creation is now idempotent. Neither is
|
||||
included in the measured table above.
|
||||
|
||||
## Original investigation limits
|
||||
|
||||
- A's outgoing summary request was only 24,557 bytes. The separate 32 MiB tool
|
||||
corpus stresses outline reads, **not** summary-context assembly. This result
|
||||
excludes neither large-context serialization/parsing nor tool/provider-specific
|
||||
synchronous work during actual compaction.
|
||||
- Manual summary compaction under a synthetic compatible provider was exercised,
|
||||
not provider-native checkpoint APIs or automatic context-overflow recovery.
|
||||
- No CodeNomad HTTP proxy, renderer, reconnection or remote TCP path is present.
|
||||
Their independently reproduced defects must not be dismissed by this result.
|
||||
- The current cooperating outline source was tested; this is not a native
|
||||
baseline/patch A/B or a general CPU-throughput comparison.
|
||||
|
||||
No product correction followed from that small-context non-reproduction. The
|
||||
requested bounded large-A qualification is recorded next; runtime/platform
|
||||
qualification and exact CPU attribution remain separate.
|
||||
|
||||
## Follow-up: real 2/8/32 MiB A context
|
||||
|
||||
### Fixture changes and size validation
|
||||
|
||||
`--context-mib 2|8|32` imports native user/assistant-tool pairs into A, using
|
||||
templates generated by real native tool execution. Each tool has a distinct
|
||||
canary and 256 KiB of ASCII text; one small trailing turn is retained separately.
|
||||
The fixture rereads native context and asserts the exact message/tool counts
|
||||
and total stored output bytes before measuring anything.
|
||||
|
||||
In this stress mode only, automatic compaction is disabled, `keep.tokens=0` and
|
||||
the synthetic model advertises a 16,777,216-token context limit. This avoids
|
||||
testing a silently shortened request instead of the requested byte budget. It
|
||||
is deliberately not a typical production model policy. The V2 compaction guide
|
||||
documents the retained tail and model-budget shortening behavior.
|
||||
|
||||
An initial development check exposed exactly that distinction: without a small
|
||||
trailing turn, the first 2 MiB request contained only 7 of 8 payload markers.
|
||||
The fixture failed rather than reporting the stored size as the compacted size.
|
||||
Putting the current turn after the large summarized prefix corrected the corpus;
|
||||
all subsequent runs assert all markers **and every payload-fill byte** at the
|
||||
actual provider boundary. There is no direct SQLite seeding or mutation.
|
||||
|
||||
| A tool payload | Native messages (pairs + tail) | Payload markers received | Provider JSON bytes | Provider messages |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| 2 MiB (2,097,152 bytes) | 18 | 8/8 | 2,123,428 | 26 |
|
||||
| 8 MiB (8,388,608 bytes) | 66 | 32/32 | 8,421,286 | 98 |
|
||||
| 32 MiB (33,554,432 bytes) | 258 | 128/128 | 33,612,826 | 386 |
|
||||
|
||||
Provider message counts differ from stored native counts because lowering adds
|
||||
separate assistant tool-call/tool-result messages and the summary instructions.
|
||||
These are compaction request sizes, not the earlier separate outline corpus.
|
||||
`--skip-outline` omits that separate 32 MiB import/RPC so the assembly matrix is
|
||||
not confused with outline load. Allowed A sizes are capped at 32 MiB, sequential
|
||||
runs create only one private native child/provider each, and no size escalation
|
||||
or runtime installation was performed.
|
||||
|
||||
### Phase measurements and causal checks
|
||||
|
||||
Immediately upon compact submission, info and B-get probes start, together with
|
||||
a B prompt/wait. Their start/finish times are retained, with explicit booleans
|
||||
for completion before provider headers/body. The provider records its handler
|
||||
start (headers observable), complete body receipt and JSON parse separately.
|
||||
The output distinguishes submission-to-arrival from **admission response** to
|
||||
arrival; neither is mislabeled completed compaction duration.
|
||||
|
||||
All three witness calls must be issued before A's provider handler begins.
|
||||
Their issuance is a client-call timestamp, not an upstream route-entry trace.
|
||||
B succeeds before releasing A in every run. An additional B prompt is then
|
||||
tested during the provider hold, both sessions must be running simultaneously,
|
||||
and A's completed context/ended event is required after release. No timing
|
||||
threshold was introduced in product or fixture assertions.
|
||||
|
||||
Two sequential runs at each size (no simultaneous fixture launch):
|
||||
|
||||
| Size / run | Compact admission | Submission → headers / full body | Max info / B-get wholly before headers | Early B prompt admission | Early B wait | B done before headers? |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | --- |
|
||||
| 2 / `IO0hKV` | 6.4 ms | 31.2 / 33.9 ms | 2.5 / 24.6 ms | 26.4 ms | 27.2 ms | no |
|
||||
| 8 / `mrXUsj` | 8.7 ms | 119.7 / 130.4 ms | 10.2 / 55.9 ms | 59.0 ms | 38.7 ms | yes |
|
||||
| 32 / `I1Py1V` | 31.8 ms | 224.8 / 309.5 ms | 47.4 / 57.3 ms | 9.4 ms | 32.1 ms | yes |
|
||||
| 2 / `gomUTk` | 8.5 ms | 56.2 / 66.1 ms | 3.5 / 36.9 ms | 48.7 ms | 57.1 ms | no |
|
||||
| 8 / `ShUeBE` | 24.3 ms | 163.5 / 173.4 ms | 83.7 / 83.2 ms | 28.5 ms | 100.5 ms | yes |
|
||||
| 32 / `jKWuew` | 45.8 ms | 290.9 / 332.7 ms | 83.2 / 82.3 ms | 11.3 ms | 52.7 ms | yes |
|
||||
|
||||
Requests *started* before headers can straddle arrival; their maxima were
|
||||
84.7/84.3 ms for info/B-get in the final 32 MiB run. The table's wholly-before
|
||||
filter excludes that fixture's provider body parsing from those latency values.
|
||||
For the final 2/8/32 runs, admission-response → provider headers was
|
||||
47.6 / 139.0 / 244.9 ms. Local provider JSON parsing was separately
|
||||
2.7 / 7.3 / 29.9 ms; it is not native assembly CPU.
|
||||
|
||||
During the held-response phase of the matrix, native reads were generally a few
|
||||
milliseconds (maximum 18.1 ms); B prompt admission was 6.1–10.0 ms. B completed
|
||||
without releasing A at every size, then A completed 12.4–19.4 ms after release.
|
||||
All runs observed started, 29 compaction deltas and ended, with no failed event.
|
||||
Observed session-event lag in the assembly sampling phase reached 128 ms, versus
|
||||
28 ms maximum during the B/provider-held phase. These are delivery observations,
|
||||
not internal queue-depth or losslessness measurements.
|
||||
|
||||
### Attribution and limits
|
||||
|
||||
Confirmed: the pre-provider preparation/dispatch window grows with this stress
|
||||
corpus, and unrelated native operations sometimes experience transient latency
|
||||
inside that window. It cannot be attributed to the deliberately held summary
|
||||
response: the provider hold has not begun for the wholly-before samples. B also
|
||||
completes before A reaches the provider in the 8/32 MiB runs, ruling out a
|
||||
continuous global stall throughout that preparation window in these executions.
|
||||
|
||||
Not confirmed: one particular synchronous SQLite step, tokenizer, JSON encoder,
|
||||
lock or upstream function as the cause. The distribution differs by operation
|
||||
and run, with fast info responses alongside slower B-get/admission in some
|
||||
samples. Host scheduling, native GC/storage work and fixture scheduling remain
|
||||
possible contributors. There is no native CPU profile or route-entry trace;
|
||||
HTTP latency alone does not prove uninterrupted event-loop blocking. No upstream
|
||||
or CodeNomad production patch is justified by this bounded evidence alone.
|
||||
|
||||
A compatibility smoke run with **2 MiB A plus the separate outline enabled**
|
||||
also passed (`6OnLuq`). It verified all A bytes plus 13 completed native
|
||||
heartbeats during a 218.2 ms outline RPC. That extra run is outside the matrix:
|
||||
its pre-header B-get/prompt were 89.1/93.5 ms and seed-phase event lag reached
|
||||
2.56 s. This additional variability reinforces the host/seed/GC qualification
|
||||
limits; it must not be hidden or mislabeled a persistent compaction freeze.
|
||||
|
||||
The corpus is synthetic repeated ASCII, with imported historical templates,
|
||||
fixture token usage and an artificially permissive model budget. It tests real
|
||||
assembly of bounded bytes, not the reporter's exact token/JSON/provider workload,
|
||||
automatic recovery, provider-native checkpoints or remote Linux/Windows HTTP.
|
||||
|
||||
### Reproduction / next step
|
||||
|
||||
```powershell
|
||||
node scripts/test-compaction-isolation-native.mjs `
|
||||
'C:\Users\Admin\AppData\Roaming\npm\node_modules\@opencode\cli\bin\opencode.exe' `
|
||||
--context-mib 32 --skip-outline
|
||||
```
|
||||
|
||||
Use `2` and `8` for the other bounded sizes; retain the TEMP/TMP setup above.
|
||||
All seven successful runs retain `result.json` and `server.log` under the same
|
||||
private `compaction-isolation-<run>` namespace. Every child was stopped and its
|
||||
exit awaited; the script naturally exited. `node --check` passes. Only this
|
||||
script and this report were edited for the follow-up.
|
||||
|
||||
Smallest useful next step: profile the **native pre-provider preparation window**
|
||||
and correlate native route-entry/exit and event-loop work under a controlled
|
||||
host, at the same fixed byte budgets. Preserve the verified transmitted bytes
|
||||
and the wholly-before-header filter. Do not patch an unproven upstream hotspot
|
||||
or reinterpret provider waiting as freeze; runtime 2.0.19 and the real remote
|
||||
platform remain separate, untested qualifications.
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
# Issue #824 — independent outline-reader review
|
||||
|
||||
Baseline reviewed: `366830f6`. Scope: the current `outline-index.ts` change,
|
||||
its responsiveness regression test and profiler, and the related performance
|
||||
claims. No product code was changed during this review.
|
||||
|
||||
## Verdict
|
||||
|
||||
Approved with no blocking finding. The patch addresses the demonstrated
|
||||
event-loop starvation while preserving the outline protocol and its existing
|
||||
correctness fences. The documentation correctly limits the result to improved
|
||||
cooperation: it does not claim lower total CPU, a hard 8 ms deadline, or that
|
||||
this defect alone explains issue #824.
|
||||
|
||||
## Correctness review
|
||||
|
||||
- `.all()` was replaced by iteration over the same header statement; the SQL,
|
||||
parameters, ordering and 512-row limit are unchanged. Projection likewise
|
||||
retains its existing SQL and response mapping.
|
||||
- A direct baseline/current comparison produced byte-equivalent JSON results,
|
||||
including checkpoint digests, for an initial mixed outline, a persisted-known
|
||||
delta after delete/update/append, and an ownership/revert-bounded read.
|
||||
- Existing navigation coverage still verifies the 16,384-entry page bound,
|
||||
cursor snapshot (`through`) behavior, deletion holes, tail checkpoint growth,
|
||||
pruning detection, tool metadata, ownership and staged-revert visibility.
|
||||
- The read transaction still begins before ownership and bounds are read and is
|
||||
held across all cooperative yields, so a page observes one snapshot. An abort
|
||||
from either new loop rejects through `yieldTurn`; iterator cleanup is followed
|
||||
by the existing unconditional `ROLLBACK`. Both new tests observe
|
||||
`db.isTransaction === false` afterward.
|
||||
- Each RPC obtains its own read-only `DatabaseSync` connection in
|
||||
`withHistoryDatabase`. Yielding with this transaction open therefore does not
|
||||
expose a shared connection to another RPC. It can retain a read snapshot for
|
||||
longer in wall-clock time, but work remains bounded and promptly cancellable.
|
||||
- `EXPLAIN QUERY PLAN` for the header query, both with and without the revert
|
||||
`id < ?` predicate, uses
|
||||
`session_message_session_seq_idx (session_id=? AND seq>? AND seq<?)`. The
|
||||
patch does not change query plans or introduce a sort scan.
|
||||
|
||||
## Responsiveness evidence
|
||||
|
||||
The regression design is discriminating rather than timing-sensitive. Its real
|
||||
SQLite view charges deterministic synthetic time whenever payload data is
|
||||
accessed, while `setImmediate` supplies an actual pending cancellation.
|
||||
|
||||
- Current code: both header and projection cancellation cases passed.
|
||||
- The same test copied outside the worktree with only `outline-index.ts`
|
||||
replaced by the exact `366830f6` source failed both cases: the header case
|
||||
consumed 896 expensive payload accesses before cancellation ran; the
|
||||
projection case consumed 384 after its 512 free header accesses.
|
||||
- A smaller independent profile (`128` rows × `256 KiB`) observed current
|
||||
maximum turn gaps of `8/8/8 ms`, versus baseline `40/43/38 ms`. Total scan
|
||||
times were comparable (`43–45 ms` current, `39–43 ms` baseline), supporting
|
||||
the documented cooperation claim without implying throughput improvement.
|
||||
- One SQLite iterator step remains synchronous. A single exceptionally costly
|
||||
row can exceed the 8 ms budget before JavaScript regains control; this
|
||||
limitation is explicitly documented in both code and investigation notes.
|
||||
|
||||
## Validation performed
|
||||
|
||||
```text
|
||||
node --import tsx --test \
|
||||
packages/server/src/opencode/session-pruning/outline-responsiveness.test.ts \
|
||||
packages/server/src/opencode/session-pruning/navigation.test.ts
|
||||
=> 11 passed, 0 failed
|
||||
|
||||
npm run typecheck --workspace @neuralnomads/codenomad
|
||||
=> passed
|
||||
|
||||
git diff --check 366830f6 -- \
|
||||
packages/server/src/opencode/session-pruning/outline-index.ts
|
||||
=> passed (only Git's existing LF/CRLF working-copy warning)
|
||||
```
|
||||
|
||||
The coordinator's full isolated OpenCode 2.0.21 fixture result was not rerun in
|
||||
this read-only review; the focused native-SQL and contract checks above are
|
||||
independent of that reported qualification.
|
||||
|
||||
## Residual risk / next step
|
||||
|
||||
No adjustment is required for this patch. Retain the explicit limitation that
|
||||
cooperative iteration cannot preempt one SQLite step. If real-session evidence
|
||||
later shows individual JSON projection steps exceeding the acceptable latency,
|
||||
the smallest follow-up is architectural (worker-thread/off-loop projection or a
|
||||
less parse-intensive stored index), not a tighter JavaScript timer threshold.
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
# #824 — récupération du correctif de rendu des tâches imbriquées
|
||||
|
||||
2026-10-01, worktree `issue-824-performance`, HEAD `366830f6`.
|
||||
|
||||
## Résultat et portée
|
||||
|
||||
Le correctif minimal de `task.tsx` est maintenant présent dans le worktree mission,
|
||||
sans commit/push ni déploiement. Deux régressions rouges sont reproduites sur la
|
||||
source baseline ; la correction passe les **63 scénarios** des quatre suites
|
||||
navigateur ciblées, naturellement, sans force-exit, skip ou force-click ajouté.
|
||||
|
||||
La mesure A/B finale, 30 échantillons par variante, observe une médiane de temps
|
||||
main-thread **353,39 -> 22,72 ms** par delta dans le fixture, pas dans l'application
|
||||
desktop. Le contrôle instrumenté confirme **80 retraits + 80 insertions -> zéro**,
|
||||
avec **80 snapshots conservés**. Ce patch n'élimine pas les relectures natives.
|
||||
|
||||
Seuls ces six fichiers appartiennent à cette tâche :
|
||||
|
||||
| Fichier | Delta / taille |
|
||||
| --- | --- |
|
||||
| `packages/ui/src/components/tool-call/renderers/task.tsx` | +29/-18, 647 lignes |
|
||||
| `packages/ui/tests/browser/render-cost.test.ts` | nouveau, 245 lignes |
|
||||
| `packages/ui/tests/browser/fixtures/render-cost.tsx` | nouveau, 150 lignes |
|
||||
| `packages/ui/tests/browser/fixtures/render-cost-timeline.tsx` | nouveau, 44 lignes |
|
||||
| `scripts/test-render-cost-audit.mjs` | nouveau, 57 lignes |
|
||||
| `dev-docs/issue-824-render-recovery.md` | ce rapport |
|
||||
|
||||
Aucun changement de cette tâche dans les stores, `server-events`, `opencode-service`,
|
||||
styles, dépendances ou serveur. Les modifications concurrentes de ces surfaces dans
|
||||
le worktree commun ne sont pas notre livraison. Aucun daemon partagé, profil,
|
||||
desktop, réseau utilisateur ou base utilisateur utilisé ; aucune dépendance
|
||||
réinstallée/symlinkée. Les HTTP fixtures sont loopback et bloquent les autres origines.
|
||||
|
||||
## Provenance et logique
|
||||
|
||||
Source : `audit-resume-integration`, correction du précédent audit de rendu.
|
||||
Le blob baseline de `task.tsx` est identique à `47f9e43e` et `366830f6` :
|
||||
`dee90df8560b83958dbf6d6f7c40e013bbdb289f`.
|
||||
Le postimage produit canonique Git est identique au correctif audit :
|
||||
`9a427900afcad8f274db5d9a77600836f24a728e`.
|
||||
Les SHA bruts diffèrent pour ce seul fichier à cause des fins de ligne du checkout ;
|
||||
comparaison LF-normalisée et `git diff --no-index` : aucun delta de contenu.
|
||||
|
||||
Les deux fixtures et le runner A/B sont repris byte-for-byte de l'audit. Le test
|
||||
était aussi identique avant une adaptation de harness : chaque serveur utilise
|
||||
désormais un cache Vite temporaire unique, supprimé après fermeture. Cela sépare
|
||||
ses modules optimisés des autres fixtures concurrents, sans changer les assertions.
|
||||
|
||||
La correction conserve les clés affichées pendant une relecture autoritaire,
|
||||
réindexe dès son retour, et maintient une shell par identité de tool avec getters
|
||||
réactifs. Un changement de clé remonte bien une shell neuve. Une page autoritaire
|
||||
vide supprime les étapes ; une erreur de lecture conserve le dernier affichage et
|
||||
le retry explicite. Markdown utilise son hash existant plutôt qu'une révision
|
||||
normalisée à zéro ; la révision de part reste suivie pour les updates in-place.
|
||||
|
||||
## Reproductions fonctionnelles exécutées ici
|
||||
|
||||
Node `v25.2.1`, Windows, Chromium Playwright `153.0.8010.12`, Vite development.
|
||||
|
||||
Contrôle rouge par override Vite de la source exacte baseline, sans réécrire le
|
||||
renderer du checkout : deux assertions échouent avec messages attendus, pas timeout :
|
||||
|
||||
1. `authoritative reprojection must not recreate the tool shell` ;
|
||||
2. `the native record changed but the displayed Markdown is stale`.
|
||||
|
||||
Le sample instrumenté baseline passe son contrôle de 80 étapes et compte 80 ajouts,
|
||||
80 retraits et 80 clones à chacun des dix deltas. La commande rouge a donc un exit 1
|
||||
attendu (2 FAIL + 1 PASS) ; le wrapper de collecte garde cet exit dans le log.
|
||||
|
||||
La suite verte finale passe **63/63**, zéro skip/failure, exit 0 naturel (~202 s) :
|
||||
render-cost (8), session-rendering (46), task-copy (6), tool-images (3). Elle exerce
|
||||
les vrais composants Solid, stores et dispatcher SSE, avec seulement le transport
|
||||
natif et les réponses API synthétiques. Le maintien du scroller pendant MMB, du
|
||||
scrollTop et disclosure, les copies exactes, l'update in-place, l'erreur/retry,
|
||||
la suppression et le changement de clé sont réellement vérifiés.
|
||||
|
||||
`npm run typecheck --workspace @codenomad/ui` passe, y compris au dernier replay.
|
||||
`git diff --check` des fichiers de livraison passe.
|
||||
Le test est admis par le glob existant `tests/browser/*.test.ts` du script package ;
|
||||
aucune modification de gate/manifest n'est nécessaire.
|
||||
|
||||
## A/B CPU actuel — séparé des résultats historiques
|
||||
|
||||
Protocole inchangé : 80 étapes read terminées × 8 000 caractères, collapsed ;
|
||||
3 warmup deltas puis 10 deltas isolés dans chacun des trois couples baseline/fixed.
|
||||
Processus/pages frais, alternance baseline puis fixed ; compteur clone et observer
|
||||
DOM **désactivés** pour les timings. CDP cumule les durées jusqu'après hydration et
|
||||
deux animation frames. Les coûts sync seuls n'incluent pas le travail différé.
|
||||
|
||||
| ms/delta, médiane [min–max], écart-type échantillon | Baseline | Correction |
|
||||
| --- | --- | --- |
|
||||
| main-thread tasks | 353,39 [189,76–737,96], 141,77 | 22,72 [11,00–68,03], 12,39 |
|
||||
| script | 18,65 [14,75–57,85], 11,10 | 0,92 [0,29–40,30], 10,04 |
|
||||
| layout | 3,58 [2,93–5,39], 0,63 | 0,14 [0,09–0,36], 0,05 |
|
||||
| recalcul style | 11,69 [10,39–14,35], 0,94 | 0,20 [0,11–0,32], 0,06 |
|
||||
| dispatcher sync | 17,25 [11,20–33,60], 4,88 | 0,25 [0,10–0,50], 0,11 |
|
||||
|
||||
L'audit antérieur rapportait 369,98 -> 20,64 ms sur 30 échantillons par variante.
|
||||
Ce ne sont pas les échantillons actuels et ils ne sont pas fusionnés avec eux.
|
||||
Le premier A/B actuel était 353,90 -> 17,03 ms ; le replay suivant 378,59 -> 15,61 ms.
|
||||
Ces variations ne sont pas une garantie de latence/FPS et aucun seuil CI absolu
|
||||
n'est ajouté.
|
||||
|
||||
## Tentatives et limites conservées
|
||||
|
||||
Deux reprises A/B supplémentaires ont échoué avant le montage du fixture fixed,
|
||||
sur `page.waitForFunction(window.fixture)` après 30 s. Elles restent dans
|
||||
`codenomad-render-cost-lehbGS/3-fixed.log` et `codenomad-render-cost-FN0wKE/1-fixed.log`
|
||||
sous le temp approuvé. Pas de gain CPU calculé depuis ces lots incomplets.
|
||||
Leurs erreurs sont de démarrage du harness, pas une assertion renderer. La
|
||||
collision du cache Vite partagé est une **hypothèse**, pas une cause démontrée :
|
||||
après isolation du cache, les six invocations A/B et 63 tests passent, mais il n'y
|
||||
a pas eu de contrôle négatif causal du cache en concurrence.
|
||||
|
||||
Des snapshots SHA avant/après des sources ont aussi détecté des mutations de tâches
|
||||
concurrentes (stores compaction, event handlers, serveur et ressources générées).
|
||||
Les cinq fichiers exécutables de notre livraison sont restés inchangés pendant
|
||||
le dernier A/B + suite verte. Ces runs **ne sont pas une qualification d'une
|
||||
composition intégrée entièrement immuable** : refaire le replay indépendant sur
|
||||
une composition figée après intégration des autres correctifs. Les wrappers
|
||||
marquent cette dérive par un exit 1, distinct des enfants A/B et tests, tous exit 0.
|
||||
|
||||
Pas de qualification production-build, Electron/WebView2, Linux/macOS, heap,
|
||||
GPU/FPS, backend réel, compactage natif ou freeze applicatif universel. Le coût des
|
||||
clones/relectures demeure. `task.tsx` reste **647 lignes**, au-dessus du seuil de
|
||||
signalement ~500 ; aucun refactor de taille seul n'a été introduit.
|
||||
|
||||
## Artefacts et commandes
|
||||
|
||||
Tous les chemins ci-dessous sont sous
|
||||
`C:/Users/Admin/AppData/Local/Temp/opencode/` :
|
||||
|
||||
- `issue-824-render-isolated-xn3MnK/red-final.log`
|
||||
SHA256 `947bb2f6beae52db032288e8f855af27fcaa7deb90a3787a85fdf49f82a06c18`.
|
||||
- `issue-824-render-isolated-xn3MnK/green.log`
|
||||
SHA256 `15b7d31be5b99cbc8e3bf62f322fa5b14a1f710e740a5cd0b2a8e0d4c718a7bd`.
|
||||
- `issue-824-render-isolated-xn3MnK/{before,after,result}.json` : dérive de composition.
|
||||
- `codenomad-render-cost-dD0phn/metrics.json` et six logs adjacents : A/B final.
|
||||
SHA256 metrics `f1a2a4a7ce5ca6f042328bc5d8e84fc315be49c90a5f26a67759859d3e801ba8`.
|
||||
|
||||
SHA256 produit brut livré : `task.tsx` =
|
||||
`d130c5fb2219772c5de0c63393744783cdb659734024f88cbe73cafeec818100`.
|
||||
SHA256 test final = `4abe4345a3a5f1a00d0e94544fd02b70851d07efa6b294a55466bc51ef7d2fde`.
|
||||
|
||||
```powershell
|
||||
$env:TEMP='C:\Users\Admin\AppData\Local\Temp\opencode'
|
||||
$env:TMP=$env:TEMP
|
||||
# Dans packages/ui, override baseline pour les assertions rouges uniquement :
|
||||
$env:CODENOMAD_RENDER_TASK_BASELINE='<temp>/task-baseline.tsx'
|
||||
node --import tsx --test --test-concurrency=1 --test-name-pattern='^(unrelated child text|native page output|audit sample:)' tests/browser/render-cost.test.ts
|
||||
Remove-Item Env:CODENOMAD_RENDER_TASK_BASELINE
|
||||
node --import tsx --test --test-concurrency=1 tests/browser/render-cost.test.ts tests/browser/session-rendering.test.ts tests/browser/task-copy.test.ts tests/browser/tool-images.test.ts
|
||||
# À la racine :
|
||||
node scripts/test-render-cost-audit.mjs 3
|
||||
npm run typecheck --workspace @codenomad/ui
|
||||
```
|
||||
|
||||
Prochaine étape minimale : revue indépendante des six fichiers de cette livraison,
|
||||
puis replay des deux assertions rouges, 63 contrôles verts et A/B sur une composition
|
||||
figée. Aucun restart/déploiement ni élargissement du patch n'est nécessaire.
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
# Issue #824 — revue indépendante auth et admission HTTP
|
||||
|
||||
## Verdict
|
||||
|
||||
**Gates auth et admission HTTP : acceptés, sans finding bloquant.**
|
||||
|
||||
La revue porte sur le diff intégré non commité de
|
||||
`D:\CodeNomad\.codenomad\worktrees\issue-824-performance` depuis `366830f6`,
|
||||
et non sur les worktrees sources. Les trois fichiers produit transport intégrés
|
||||
ont exactement les mêmes blobs Git que `d9598e42`. Les changements UI, relais
|
||||
d'événements, outline et rendu présents simultanément dans le worktree sont hors
|
||||
périmètre.
|
||||
|
||||
Ces corrections ont une causalité plus étroite que l'intitulé général #824 :
|
||||
|
||||
- l'auth retire les dérivations scrypt HTTP du thread principal Node ;
|
||||
- l'admission HTTP retire un observateur local abandonné pendant ses préflights
|
||||
et empêche ses continuations tardives ;
|
||||
- elles ne démontrent ni la cause du blocage natif pendant une vraie compaction,
|
||||
ni une saturation HTTP/1 Electron Linux → backend Windows, ni l'élimination du
|
||||
freeze global rapporté.
|
||||
|
||||
Les deux rapports d'intégration conservent correctement cette limite et ne
|
||||
promettent pas une correction universelle.
|
||||
|
||||
## Findings prioritaires
|
||||
|
||||
### Aucun finding P0/P1/P2
|
||||
|
||||
Je n'ai trouvé ni autorisation accordée sur un ancien mot de passe, ni replay de
|
||||
mutation, ni invalidation de connexion partagée sur abandon local, ni forwarding
|
||||
après annulation de préflight dans le périmètre revu.
|
||||
|
||||
### Risques résiduels non bloquants
|
||||
|
||||
1. **Saturation du pool libuv non qualifiée.**
|
||||
`packages/server/src/auth/password-hash.ts:58-64` rend le thread HTTP
|
||||
coopératif via `crypto.scrypt`, mais ne supprime pas le coût CPU/mémoire et ne
|
||||
borne pas une rafale de logins. Il n'existait pas de rate limiter sur ces
|
||||
routes dans la baseline et le patch n'en retire aucun. Une politique
|
||||
d'admission serait un changement séparé, non requis pour accepter ce patch.
|
||||
2. **Persistance auth toujours synchrone.**
|
||||
`packages/server/src/auth/auth-store.ts:176-184` conserve les petites
|
||||
écritures synchrones existantes. Les mesures et le correctif concernent la
|
||||
dérivation scrypt, pas une garantie d'absence totale de pause filesystem.
|
||||
3. **Qualification réseau/OS encore absente.**
|
||||
Les tests transport utilisent de vrais sockets loopback et le client SDK,
|
||||
mais pas Electron Linux vers Windows, suspension/relogin, proxy intermédiaire
|
||||
ou limite de connexions Chromium. Le correctif prouve la fuite d'observateur
|
||||
et ses fences, pas le scénario utilisateur complet.
|
||||
|
||||
## Revue auth
|
||||
|
||||
- `packages/server/src/auth/password-hash.ts:23-55` conserve le record v1, le
|
||||
sel 16 octets, la clé 64 octets, les paramètres enregistrés et
|
||||
`crypto.timingSafeEqual`. Le seul chemin synchrone restant est explicitement
|
||||
`hashPasswordSync`, appelé pendant l'initialisation CLI/env avant service.
|
||||
- `packages/server/src/server/routes/auth.ts:101,150` attend les résultats login
|
||||
et password ; la recherche dépôt ne trouve pas d'autre caller produit oublié
|
||||
de `validateLogin`, `validateCredentials`, `setPassword` ou `hashPassword`.
|
||||
- `packages/server/src/auth/auth-store.ts:117-160` sérialise les changements par
|
||||
ordre d'admission. La queue absorbe un rejet uniquement pour permettre le
|
||||
changement suivant, tandis que le caller original reçoit bien l'erreur. Une
|
||||
persistence bootstrap échouée ne supprime pas `bootstrapUsername` et ne publie
|
||||
pas de nouveau cache.
|
||||
- `packages/server/src/auth/auth-store.ts:101-115` relit l'identité du record
|
||||
cached après l'await scrypt. Un login dérivé sur l'ancien record est refusé si
|
||||
une mutation a persisté entre-temps, sans recalcul ni replay.
|
||||
- Les réponses 401/409, le cookie, le statut bootstrap, l'override CLI/env et
|
||||
l'auth désactivée sont couverts. Aucun mécanisme de rate-limit existant n'est
|
||||
modifié.
|
||||
|
||||
## Revue admission HTTP
|
||||
|
||||
- `packages/server/src/server/request-admission.ts:8-14` observe dès l'entrée
|
||||
l'upload `aborted` et le `close` d'une réponse incomplète. Il n'interprète pas
|
||||
le `close` normal de l'IncomingMessage comme une déconnexion.
|
||||
- `packages/server/src/server/request-admission.ts:16-36` fait courir uniquement
|
||||
l'attente locale contre l'abandon. La Promise partagée reste observée ; sa
|
||||
génération n'est ni annulée ni invalidée et un rejet tardif ne devient pas une
|
||||
unhandled rejection.
|
||||
- `packages/server/src/server/http-server.ts:699-975` transmet le signal aux
|
||||
lectures natives qui le supportent (`profile`, session/project, PTY/Shell,
|
||||
ownership/location) et entoure les acquisitions/inventaires non annulables par
|
||||
`wait()`. Chaque frontière résolue revérifie l'abandon avant de poursuivre.
|
||||
- Les guards précédant la fence de mutation, l'environnement et le forwarding
|
||||
sont présents à `http-server.ts:975`, `989-1015` et `1022-1024`. Le `finally`
|
||||
de l'entrée dispose les listeners sur toutes les sorties.
|
||||
- `packages/server/src/opencode/compatibility/proxy.ts:27-40` installe le listener
|
||||
de forwarding avant le fetch puis refuse immédiatement un downstream déjà
|
||||
fermé. Après admission native, la sémantique antérieure demeure : abandon de
|
||||
la réponse HTTP sans replay ni invalidation de la connexion partagée.
|
||||
- `packages/server/src/workspaces/manager.ts:270-286` propage le signal à la
|
||||
validation native de location. Une annulation éventuellement convertie en
|
||||
`false` par son `catch` est réémise par le `wait()` appelant, dont le signal est
|
||||
alors aborté ; elle ne devient donc pas une autorisation ou un 403 tardif.
|
||||
|
||||
## Validation fraîche
|
||||
|
||||
Exécutée depuis le worktree intégré, Windows, Node 25.2.1, sans daemon/profil/base
|
||||
utilisateur, restart ou déploiement :
|
||||
|
||||
```powershell
|
||||
node --unhandled-rejections=strict --import tsx --test packages/server/src/auth/password-hash.test.ts packages/server/src/server/routes/auth-responsiveness.test.ts packages/server/src/server/routes/auth.test.ts
|
||||
node --unhandled-rejections=strict --import tsx --test packages/server/src/server/__tests__/instance-proxy-disconnect-diagnostic.test.ts packages/server/src/server/request-admission.test.ts packages/server/src/server/__tests__/instance-proxy.test.ts packages/server/src/server/__tests__/instance-proxy-legacy.test.ts packages/server/src/opencode/compatibility/proxy.test.ts
|
||||
npm run typecheck --workspace @neuralnomads/codenomad
|
||||
$env:TEMP='C:\Users\Admin\AppData\Local\Temp\opencode'
|
||||
$env:TMP=$env:TEMP
|
||||
node --unhandled-rejections=strict --import tsx --test "packages/server/src/**/*.test.ts"
|
||||
```
|
||||
|
||||
Résultats :
|
||||
|
||||
- auth ciblé : **9/9 pass**, sortie naturelle ;
|
||||
- transport ciblé : **65/65 pass**, sortie naturelle ;
|
||||
- typecheck serveur : **pass** ;
|
||||
- gate serveur complet intégré : **799 tests, 793 pass, 0 fail, 6 skips
|
||||
déclarés**, sortie naturelle en environ 35,7 s ;
|
||||
- `git diff --check` ciblé : aucune erreur whitespace (les commandes
|
||||
`--no-index` retournent normalement 1 parce que les nouveaux fichiers
|
||||
diffèrent de `NUL`; seulement les avertissements LF/CRLF habituels).
|
||||
|
||||
Le gate complet contient aussi les autres changements concurrents déjà intégrés ;
|
||||
il confirme leur composition actuelle avec ces patches, sans attribuer leurs
|
||||
tests aux deux corrections revues.
|
||||
|
||||
## Taille et suite minimale
|
||||
|
||||
Les fichiers touchés déjà surdimensionnés restent
|
||||
`packages/server/src/server/http-server.ts` (~2346 lignes) et
|
||||
`packages/server/src/workspaces/manager.ts` (~1240 lignes). Aucun refactor de
|
||||
taille n'est justifié dans cette correction bornée.
|
||||
|
||||
Suite minimale recommandée : conserver les patches tels quels et poursuivre
|
||||
séparément la qualification avec runtime privé d'une vraie compaction et le
|
||||
scénario Electron Linux → Windows. Ne pas présenter ces deux corrections comme
|
||||
preuve que la cause native ou le freeze global #824 est résolu.
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
# #824 — correction M1 de membership des étapes imbriquées
|
||||
|
||||
## Résultat
|
||||
|
||||
Le finding M1 de `issue-824-ui-review.md` est corrigé dans le worktree mission.
|
||||
Les cinq nouvelles régressions échouent sur le renderer livré avant cette tâche,
|
||||
puis **68/68 tests navigateur** passent sur le renderer corrigé, avec rejets stricts,
|
||||
sortie naturelle, aucun skip/force-exit/force-click. Les shells conservés gardent
|
||||
leur disclosure, leur scroller et leur scrollTop après suppression ou déplacement.
|
||||
Le contrôle instrumenté de 80 étapes conserve **zéro retrait/insertion par delta**.
|
||||
|
||||
Cette tâche modifie uniquement :
|
||||
|
||||
- `packages/ui/src/components/tool-call/renderers/task.tsx` ;
|
||||
- `packages/ui/tests/browser/render-cost.test.ts` ;
|
||||
- `packages/ui/tests/browser/fixtures/render-cost.tsx` ;
|
||||
- ce rapport `dev-docs/issue-824-task-membership-fix.md`.
|
||||
|
||||
Aucun store/SDK/serveur/style, daemon, base utilisateur, profil, déploiement ou
|
||||
restart modifié. Aucun commit/push, sous-agent ni installation de dépendances.
|
||||
|
||||
## Cause et correction
|
||||
|
||||
L'index incrémental inférait les changements de membership depuis les nombres de
|
||||
messages/parts et l'identité du message de queue. Une relecture autoritaire peut
|
||||
conserver toutes ces propriétés et supprimer, remplacer, réordonner ou changer le
|
||||
type d'une part. Les shells supprimés se démontaient, mais `childToolKeys` et le
|
||||
compteur/troncature gardaient des clés fantômes. Un ajout retrouvant l'ancienne
|
||||
cardinalité restait invisible.
|
||||
|
||||
Le renderer réutilise maintenant son **scanner structurel borné existant** au point
|
||||
de révision de session. Il ne compare ni copie les payloads/output : seuls les IDs
|
||||
et le type de part déterminent les clés. On supprime les heuristiques de counts/tail
|
||||
plutôt que d'ajouter un autre cache ou une politique de refresh. La liste précédente
|
||||
est rendue telle quelle quand ses clés ordonnées sont identiques.
|
||||
|
||||
Le scan reste limité à **10 000 unités message/part** et s'arrête une fois trouvées
|
||||
201 étapes ; seules les **200 dernières** s'affichent. Les constantes et le budget
|
||||
du scanner restent inchangés. Le coût des IDs/type est payé à chaque révision,
|
||||
même si le texte seul change : c'est le compromis explicite pour reconnaître tout
|
||||
remplacement autoritaire, sans observer/comparer les gros payloads. Aucun gain CPU
|
||||
nouveau ni coût maximal de 10 000 IDs en production n'est revendiqué ici.
|
||||
|
||||
`For` remplace `Index` sur les clés tool : une part retenue suit son identité quand
|
||||
une suppression ou un réordonnancement change sa position. Une identité nouvelle
|
||||
reçoit une shell/disclosure neuve. Les getters réactifs et le hash Markdown de la
|
||||
correction précédente restent inchangés. Une lecture pendante conserve la liste
|
||||
résidente ; le retour autoritaire, une page vide et le changement de child-session
|
||||
continuent à la réconcilier. Le recalcul remet aussi la troncature à jour après shrink.
|
||||
|
||||
Delta produit relatif au renderer **déjà intégré avant cette tâche**, préimage
|
||||
conservée dans le temp : **+12/-90 lignes**. Ce n'est pas un delta contre HEAD, qui
|
||||
ne contient pas encore les correctifs mission. `task.tsx` reste **569 lignes**, au-
|
||||
dessus du seuil d'avertissement ~500 ; aucun refactor de taille indépendant.
|
||||
|
||||
## Preuves exécutées
|
||||
|
||||
Node `v25.2.1`, Windows, Chromium Playwright `153.0.8010.12` ; vrais composants
|
||||
Solid, stores et `loadMessages(..., { force: true })`, native/API réponses privées,
|
||||
réseau hors loopback bloqué. Le message natif `child-message` reste le même ; le
|
||||
fixture remplace sa liste de parts, pas le composant ni l'index.
|
||||
|
||||
Les cinq nouvelles assertions rouges causales, sans seuil timing :
|
||||
|
||||
1. `2 -> 1 -> 2` en gardant step-0 : counter reste `2 steps` au lieu de `1 steps` ;
|
||||
2. même scénario en gardant step-1 (suppression en tête) : même counter faux ;
|
||||
3. remplacement/réordre `[step-0, step-1] -> [step-2, step-0]` à cardinalité égale :
|
||||
nouvelle step-2 absente ;
|
||||
4. changement tool -> text avec ID inchangé : counter reste à 2 au lieu de 1 ;
|
||||
5. shrink de 201 à 199 parts : counter reste `200+ steps` au lieu de `199 steps`.
|
||||
|
||||
Sur correction, tous passent. Les deux premiers vérifient également l'arrivée
|
||||
exactement une fois de la nouvelle step-2, puis l'identité shell/scroller, disclosure
|
||||
ouverte et scrollTop 240 de l'étape retenue. Le troisième vérifie ordre DOM exact et
|
||||
shell retained. Le cinquième vérifie 200 shells initiales, 199 ensuite et disparition
|
||||
du diagnostic de troncature.
|
||||
|
||||
Gate final : 13 render-cost + 46 session-rendering + 6 task-copy + 3 tool-images =
|
||||
**68 scénarios distincts**, ~176 s, exit 0 naturel, aucun failure/skip. Le contrôle
|
||||
perf réel compte, pour chacun de dix deltas, **80 clones, 0 added, 0 removed** ; une
|
||||
assertion explicite interdit maintenant les remounts dans ce sample instrumenté.
|
||||
Ces compteurs sont distincts des A/B CPU antérieurs, qui restent dans leurs rapports
|
||||
et ne sont pas recyclés comme mesures de ce patch.
|
||||
|
||||
`npm run typecheck --workspace @codenomad/ui` passe sur dernier patch.
|
||||
`git diff --check` ciblé passe (avertissement LF/CRLF seulement).
|
||||
Les SHA des trois exécutables de livraison sont identiques avant/après la séquence
|
||||
rouge/verte. Cela qualifie la stabilité de ce lot, pas une composition entière
|
||||
immutable de tous les travaux de mission.
|
||||
|
||||
## Artefacts et replay
|
||||
|
||||
Sous `C:/Users/Admin/AppData/Local/Temp/opencode/` :
|
||||
`issue-824-membership-c5bca1d90b4a4564bd4c7f695b6381bd/` contient :
|
||||
|
||||
- `task-before.tsx` : préimage exacte avant correction M1 ;
|
||||
- `red.log` : 5 échecs d'assertion attendus, exit 1, naturel ;
|
||||
SHA256 `db9316eb888984364cbdd59d2fac8fac74600ed9e5e779f9c86b33869b1fc51a` ;
|
||||
- `green.log` : 68 passes, exit 0, naturel ;
|
||||
SHA256 `e4f5a996b9b576703888b353e14c02940b4dd72d0fe3e056cc54b96fcffe1849` ;
|
||||
- `result.json` : statuts enfants + empreintes avant/après, `changed: []`.
|
||||
|
||||
SHA256 produit livré :
|
||||
`f07ce171a6b1d3394ce94be496b0abd67f8bc0bb48e946eb9173a681c844f401`.
|
||||
SHA256 test : `93d9e95988b53eb1e3934ecd13f4e8d32e0eb797682a866db113797174be5075`.
|
||||
SHA256 fixture : `0df543ea983c23e33a4957ed6732dc81a2e04cf5ede7d4f1281d6ee058360ebb`.
|
||||
|
||||
Depuis `packages/ui`, avec TEMP/TMP pointant vers le temp approuvé :
|
||||
|
||||
```powershell
|
||||
$env:CODENOMAD_RENDER_TASK_BASELINE='<artefact>/task-before.tsx'
|
||||
node --unhandled-rejections=strict --import tsx --test --test-concurrency=1 --test-name-pattern='^authoritative task membership' tests/browser/render-cost.test.ts
|
||||
Remove-Item Env:CODENOMAD_RENDER_TASK_BASELINE
|
||||
node --unhandled-rejections=strict --import tsx --test --test-concurrency=1 tests/browser/render-cost.test.ts tests/browser/session-rendering.test.ts tests/browser/task-copy.test.ts tests/browser/tool-images.test.ts
|
||||
```
|
||||
|
||||
Prochaine étape : re-review indépendante du finding M1, puis validation combinée
|
||||
sur la composition figée. Pas de qualification desktop/Linux/macOS, daemon réel ou
|
||||
disparition universelle des freezes revendiquée par ce lot.
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
# Issue 824 — revue indépendante de l'intégration UI
|
||||
|
||||
## Verdict
|
||||
|
||||
**Corrections demandées avant validation finale**, pour un défaut de membership
|
||||
du renderer `task` confirmé ci-dessous. Le coalescing du compactage et la
|
||||
séparation des diagnostics SSE sont acceptables dans leur périmètre; aucun défaut
|
||||
de perte de texte, d'ordre ou de fence n'a été reproduit sur ces surfaces.
|
||||
|
||||
Revue bornée au diff depuis `366830f6` de `opencode-data.ts`, `instances.ts`,
|
||||
`compaction-delta-buffer.ts`, `task.tsx`, `server-events.ts`,
|
||||
`event-source-handlers.ts` et de leurs fixtures. Aucun fichier produit n'a été
|
||||
modifié par cette revue.
|
||||
|
||||
## Findings
|
||||
|
||||
### M1 — une suppression autoritaire d'une part tool laisse une clé fantôme dans le renderer `task`
|
||||
|
||||
Dans `task.tsx`, la mise à jour incrémentale ne traite que
|
||||
`nextPartCount > previousPartCount`. Si une page autoritaire conserve le message
|
||||
mais retire une part tool, `nextPartCount < previousPartCount` ne déclenche ni
|
||||
rescan ni retrait de `childToolKeys`.
|
||||
|
||||
La ligne supprimée disparaît malgré tout parce que `TaskToolCallRow` ne trouve
|
||||
plus sa part et son `Show` se démonte. En revanche, la clé reste dans
|
||||
`childToolKeys` : le compteur et la troncature sont faux, et un ajout ultérieur
|
||||
qui ramène le nombre de parts à sa valeur précédente peut ne pas être indexé.
|
||||
|
||||
Reproduction indépendante avec le vrai composant, le vrai store et
|
||||
`loadMessages(..., { force: true })`, via une transformation Vite en mémoire qui
|
||||
retire seulement `step-0` de la réponse native :
|
||||
|
||||
```json
|
||||
{"count":"2 steps","visible":1,"partIds":["step-1"]}
|
||||
```
|
||||
|
||||
La reproduction directe déjà exposée par le fixture donne le même état. Le test
|
||||
vert actuel `removeTool(0)` affirme uniquement que le DOM de `step-0` est détaché;
|
||||
il n'affirme ni `1 step`, ni la membership, ni l'ajout suivant. Le test de page
|
||||
vide couvre une baisse du nombre de messages et prend une autre branche de full
|
||||
rescan.
|
||||
|
||||
Correction minimale recommandée : full rescan borné dès qu'un nombre de parts
|
||||
d'un message indexé diminue (et, si les pages peuvent remplacer des identités à
|
||||
cardinalité égale, comparer aussi l'identité bornée des parts). Ajouter un cas
|
||||
page autoritaire 2→1 dans le même message, puis ajout d'une nouvelle part 1→2;
|
||||
le shell de la part non supprimée doit rester monté, le compteur doit faire
|
||||
2→1→2 et la nouvelle clé doit apparaître exactement une fois.
|
||||
|
||||
### R1 — l'accumulateur de compactage est borné dans le temps, pas en taille
|
||||
|
||||
Le timer part du premier fragment et n'est jamais repoussé : avec de vrais
|
||||
événements EventSource échelonnés, la résidence est donc bornée à environ 250 ms.
|
||||
En revanche, tant que la boucle ne rend pas la main, le nombre de fragments et la
|
||||
taille de la chaîne concaténée n'ont pas de plafond explicite. Le gros backlog
|
||||
synchrone du diagnostic peut toujours construire un unique payload volumineux.
|
||||
|
||||
Ce n'est pas bloquant pour le correctif revendiqué : le transport produit livre
|
||||
ici un événement par callback, les nouveaux tests de cadence rendent réellement
|
||||
la main, et le résumé final doit de toute façon exister en mémoire. Ne pas appeler
|
||||
le buffer « borné en mémoire ». Si une mesure réelle montre une rafale sans
|
||||
yield, ajouter une limite qui force une réduction intermédiaire sans supprimer ni
|
||||
réordonner de texte; ne pas introduire un drop silencieux.
|
||||
|
||||
### T1 — le test store compactage ne fournit pas une sortie naturelle
|
||||
|
||||
Les 11 assertions passent avec rejets stricts, mais le processus reste vivant
|
||||
au-delà de 120 s sans `--test-force-exit`. Avec l'option de force, 11/11 passent
|
||||
en 0,77 s. Cela correspond à la convention déjà déclarée pour les stores importés
|
||||
et ne démontre pas une fuite du nouveau buffer, mais ce fichier seul ne constitue
|
||||
pas un gate de lifecycle naturel. Les 10 tests navigateur intégrés ci-dessous
|
||||
sortent naturellement.
|
||||
|
||||
## Contrats compactage vérifiés
|
||||
|
||||
- Chaque delta avance immédiatement `messageRevisions`, `fullDataRevisions` et
|
||||
la fence de lecture du reducer observé; l'agrégat réentrant n'avance pas ces
|
||||
révisions une seconde fois.
|
||||
- Un buffer est séparé par couple instance/session. Un événement non-delta de la
|
||||
même session force l'ordre; les autres sessions et instances ne forcent pas le
|
||||
flush.
|
||||
- `ended` et `failed` annulent les fragments car le SDK 2.0.21 remplace le résumé
|
||||
par `ended.data.text` ou remplace la ligne par l'erreur. Delete, reconnect,
|
||||
prune, revert committed et dispose annulent aussi avant qu'un timer puisse
|
||||
ressusciter le contenu.
|
||||
- Une lecture SDK déclenchée avant admission est refusée par révision. Une lecture
|
||||
après admission force d'abord l'agrégat; la page native qui contient déjà ce
|
||||
texte n'est pas réappendue.
|
||||
- Rotation à 200 messages, idle, nouvelle exécution et second start conservent
|
||||
l'ordre. La queue de rotation et la resynchronisation autoritaire restent les
|
||||
fences existantes.
|
||||
- Une session inactive jamais chargée ne crée pas de transcript ni de payload de
|
||||
reducer; seules les fences scalaires et le marqueur de compaction inachevée
|
||||
persistent. End/failed/dispose les retirent. Une activation intermédiaire passe
|
||||
par la page autoritaire, conserve la fenêtre UI de 200 messages et reprend
|
||||
ensuite le streaming sans duplication.
|
||||
- Une session chargée puis inactive réduit l'agrégat mais n'hydrate pas sa vue
|
||||
cachée; son retour recharge le texte exact.
|
||||
|
||||
Les cinq scénarios navigateur utilisent le vrai `SessionView`, le dispatcher
|
||||
natif et les stores. Le cas cadencé livre huit événements dans huit tâches
|
||||
séparées par 10 ms; ce n'est pas uniquement le backlog synchrone historique.
|
||||
|
||||
## Renderer imbriqué : propriétés validées hors M1
|
||||
|
||||
- Les getters réactifs mettent à jour output, copy et erreurs sans remonter le
|
||||
shell d'une clé stable; disclosure, scroller et scrollTop restent conservés.
|
||||
- Le `Show keyed` externe remonte le shell quand `Index` réutilise une position
|
||||
pour une autre clé, donc l'état d'une étape évincée n'est pas transféré.
|
||||
- Une invalidation de lecture conserve le snapshot résident; le full rescan après
|
||||
retour autoritaire et la page vide suppriment les messages obsolètes.
|
||||
- La limite de scan reste 10 000 unités et l'affichage 200 étapes. Le patch ne
|
||||
rend pas le scan ou le nombre de shells non borné.
|
||||
- L'output d'une page native à révision de part zéro utilise de nouveau son hash
|
||||
Markdown et n'affiche pas le cache de l'ancienne page.
|
||||
|
||||
## Diagnostic SSE / flush Solid
|
||||
|
||||
La correction ne prétend plus capturer une dérivation Solid dans le `try/catch`
|
||||
de chaque abonné. Elle sépare correctement les frontières : JSON invalide produit
|
||||
`Failed to parse event`; une exception qui sort du flush de `solidBatch` produit
|
||||
`Failed to dispatch event`. Elle ne déclenche ni reconnexion ni rejeu, et les
|
||||
événements suivants continuent en FIFO. Le test navigateur dérivé traverse le
|
||||
vrai EventSource et confirme les deux événements chez les abonnés natif et typé.
|
||||
|
||||
Les risques déjà acceptés restent inchangés : ce logger `debug` est désactivé par
|
||||
défaut, et la course serveur abort local / erreur amont n'est pas couverte ici.
|
||||
Aucune nouvelle promesse de diagnostic persistant ou d'isolation du flush n'est
|
||||
faite.
|
||||
|
||||
## Gates exécutés
|
||||
|
||||
- `node --unhandled-rejections=strict --import tsx --test --test-concurrency=1 packages/ui/tests/browser/compaction-responsiveness.test.ts packages/ui/tests/browser/event-subscriber-isolation.test.ts` — **10/10**, sortie naturelle.
|
||||
- `node --unhandled-rejections=strict --import tsx --test --test-concurrency=1 --test-name-pattern="^(unrelated child text|a failed child refresh|native page output|changing an indexed task key)" packages/ui/tests/browser/render-cost.test.ts` — **4/4**, sortie naturelle.
|
||||
- `node --conditions=browser --unhandled-rejections=strict --import tsx --test packages/ui/src/lib/event-source-handlers.test.ts` — **4/4**, sortie naturelle.
|
||||
- `node --conditions=browser --unhandled-rejections=strict --import tsx --test packages/ui/src/stores/opencode-compaction.test.ts` — **11 assertions vertes**, mais timeout de lifecycle à 120 s.
|
||||
- Même test avec `--test-force-exit` — **11/11**, 0 échec.
|
||||
- `npm run typecheck --workspace @codenomad/ui` — vert.
|
||||
- `git diff --check` ciblé — vert, hors avertissements LF/CRLF du worktree.
|
||||
|
||||
Les deux reproductions ad hoc du finding M1 ont utilisé un serveur Vite et un
|
||||
Chromium privés loopback, sans fichier temporaire dans le dépôt, daemon, profil,
|
||||
base utilisateur, restart ou déploiement.
|
||||
|
||||
## Taille des fichiers
|
||||
|
||||
- `packages/ui/src/stores/opencode-data.ts` : ~828 lignes, au-dessus de la cible
|
||||
de 800.
|
||||
- `packages/ui/src/stores/instances.ts` : ~2 093 lignes, très au-dessus de la
|
||||
cible.
|
||||
- `packages/ui/src/components/tool-call/renderers/task.tsx` : ~647 lignes,
|
||||
au-dessus du seuil d'avertissement.
|
||||
- `packages/ui/src/stores/compaction-delta-buffer.ts` : 56 lignes.
|
||||
|
||||
Aucun refactor de taille seul n'est recommandé dans cette mission.
|
||||
|
||||
## Addendum — disposition du finding M1
|
||||
|
||||
### Verdict de suivi
|
||||
|
||||
**M1 est clôturé.** La correction de membership intégrée après la revue initiale
|
||||
traite les suppressions, remplacements et réordonnancements autoritaires sans
|
||||
réintroduire les remounts qui motivaient le correctif de performance. Le verdict
|
||||
« corrections demandées » ci-dessus est donc levé pour cette surface. Les
|
||||
observations R1 et T1 restent des limites non bloquantes, inchangées.
|
||||
|
||||
Cette disposition repose sur une revue indépendante du nouveau diff de
|
||||
`task.tsx`, `render-cost.test.ts` et `fixtures/render-cost.tsx`; elle ne fusionne
|
||||
pas les chiffres A/B historiques avec les mesures de ce suivi.
|
||||
|
||||
### Pourquoi la clé fantôme ne subsiste plus
|
||||
|
||||
- À chaque révision de session, le renderer recalcule les identités structurelles
|
||||
dans le budget existant, au lieu de ne détecter que l'augmentation du nombre de
|
||||
parts. Une baisse 2→1, un retour 1→2, un remplacement/reorder 2→2 et une part
|
||||
passée de `tool` à `text` sont donc tous visibles.
|
||||
- Le scan reste borné par `TASK_MESSAGE_SCAN_LIMIT = 10_000` et collecte au plus
|
||||
`TASK_STEP_RENDER_LIMIT + 1`, puis affiche les 200 dernières clés.
|
||||
- Si la séquence de clés est strictement identique, le setter conserve le tableau
|
||||
précédent. Si elle change, `<For>` associe les lignes aux valeurs de clé plutôt
|
||||
qu'à leur position : les clés retenues conservent leur composant et leur état,
|
||||
tandis que les nouvelles identités obtiennent un shell neuf.
|
||||
- Le snapshot résident reste affiché pendant une lecture invalide; le rescan se
|
||||
fait sur le retour autoritaire. Une erreur conserve toujours le snapshot et le
|
||||
retry existants.
|
||||
|
||||
### Régressions discriminantes
|
||||
|
||||
Les nouveaux cas vrais composants/stores/pages natives vérifient :
|
||||
|
||||
1. suppression puis ajout dans le même message, pour chacune des deux positions
|
||||
initiales : compteur `2→1→2`, nouvelle clé unique, shell/disclosure/scroller et
|
||||
`scrollTop` de la clé retenue inchangés;
|
||||
2. remplacement et reorder à cardinalité égale : ordre exact `[step-2, step-0]`,
|
||||
`step-1` absent et shell de `step-0` conservé;
|
||||
3. identité de part identique dont le type passe de tool à texte : compteur réduit
|
||||
et ligne supprimée;
|
||||
4. fenêtre `201→199` : toujours 200 éléments maximum, puis 199, sans diagnostic de
|
||||
troncature obsolète;
|
||||
5. 80 étapes soumises aux deltas de texte non structurels : chaque échantillon
|
||||
conserve `added=0` et `removed=0`. Les 80 snapshots de données déjà documentés
|
||||
restent présents; aucune nouvelle revendication ne les élimine.
|
||||
|
||||
### Validation fraîche du suivi
|
||||
|
||||
- `node --unhandled-rejections=strict --import tsx --test --test-concurrency=1 packages/ui/tests/browser/render-cost.test.ts` — **13/13**, sortie naturelle (~45 s).
|
||||
- `node --unhandled-rejections=strict --import tsx --test --test-concurrency=1 packages/ui/tests/browser/task-copy.test.ts packages/ui/tests/browser/tool-images.test.ts` — **9/9**, sortie naturelle (~37 s).
|
||||
- `npm run typecheck --workspace @codenomad/ui` — vert.
|
||||
- `git diff --check` sur les trois fichiers revus et ce rapport — vert, hors
|
||||
avertissement LF/CRLF du worktree.
|
||||
|
||||
Échantillon descriptif frais des dix deltas à 80 étapes : temps de tâche entre
|
||||
environ **9,2 et 16,1 ms**, temps synchrone rapporté entre **0,1 et 0,3 ms**, avec
|
||||
`added=0` et `removed=0` pour chaque échantillon. Il s'agit d'un fixture Chromium
|
||||
de développement et non d'un nouveau benchmark desktop ou d'une garantie FPS.
|
||||
|
||||
Aucun daemon, profil, base utilisateur, restart, déploiement, dépendance, commit
|
||||
ou push n'a été utilisé pour ce suivi.
|
||||
Loading…
Add table
Add a link
Reference in a new issue