Skip to content

Commit bfbe88e

Browse files
authored
fix(storage): merge per-device session page logs (#245)
* fix(storage): merge single-writer session page chains * test(runtime): await persisted settings instead of fixed delays * test(storage): add opt-in multi-process Drive acceptance harness * docs: record successful real Drive sync acceptance * fix(chat): reconcile interleaved device history after restore * fix(chat): adopt merged cursor for unchanged newest messages * docs: record merged cursor regression and rebased checks --------- Co-authored-by: NubsCarson <192162056+NubsCarson@users.noreply.github.com>
1 parent 1690b0e commit bfbe88e

26 files changed

Lines changed: 2027 additions & 39 deletions

‎docs/qa/cloud-resume/README.md‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Device-scoped session pages — draft replacement for #245
2+
3+
The original branch-per-resume implementation is removed. Cloud resumes keep the canonical session ID and do not copy history. Each SessionStore has a new writer incarnation, combined with the persisted device ID, so different devices **and concurrent processes on the same device** never intentionally write the same page. Calls within a writer are serialized.
4+
5+
New keys are `{sessionId}__d{deviceId}-{writerId}__dp{N}`. The `__dp` suffix is intentional: old clients parse `__p` and would otherwise treat a device chain as an independent writable session. Legacy `{sessionId}__p{N}` pages remain readable; new clients merge them with device chains. Old clients ignore the new namespace and therefore cannot see its new messages. Two old clients can still overwrite their shared legacy chain. This is not full mixed-version sync.
6+
7+
Device pages contain the existing encrypted records. Message clocks increase monotonically and are seeded from observed history; read ordering uses timestamp and writer identity, preserving order within each chain. Concurrent replies are retained, not resolved into a single authoritative answer. Metadata is selected from the latest chain-head metadata with a deterministic writer tie-breaker. The session list has one canonical entry.
8+
9+
Merged pagination uses an opaque string cursor containing each chain's next unread position. New appends and newly discovered writers do not shift an already-started older-history traversal; a fresh load includes them. Reads start at each chain's tail and fetch earlier pages as needed, rather than copying full history on resume. Local-only first paint remains separate from cloud discovery. Existing numeric cursors and local-only legacy writes remain supported.
10+
11+
Explicit fork/export uses merged history; fork preserves message timestamps. Delete removes the currently listed chains. Full pages remain unchanged on rollover. Reconnect compares local device-page tails with cloud copies: only a strict byte-prefix extension replaces an existing cloud page; a shorter restored backup never replaces a longer cloud copy, and divergent copies produce a status error. Normal debounced uploads to the same key are serialized. Put-only local adapters append by combining bytes rather than replacing the prior log.
12+
13+
## Validation
14+
15+
`packages/core/src/runtime/cloud-resume.spec.ts` retains the original runtime/encrypted-store/mirror harness, now asserting one stable session ID with both writers' messages. It covers simultaneous resumes, Korean text, rollover, legacy source bytes, fresh cloud reads, local-only behavior, missing-page fork rejection, ephemeral sessions, and offline backfill.
16+
17+
`packages/core/src/account/devicePages.spec.ts` adds same-device parallel writers and appends; stable pagination while new data arrives; clock skew and legacy reads; remote tail and metadata refresh; offline extension of an existing cloud page; fork/delete; missing-page failure; stale-backup protection; fork timestamps; old-client namespace isolation; serialized uploads; and sealed-page immutability.
18+
19+
Final local result: **503 tests passed, 5 platform skips across 69 files**, with Chromium panel checks required. Core, panel, CLI and localhost typechecks passed; CLI, MCP, webview and VS Code builds passed. Raw test/build receipts are adjacent. The first hosted run exposed a fixed-50ms sleep in the existing settings test; it now waits for persisted messages. The full suite passed again using the CI command, recorded in `ci-mode-tests.log`. No model/provider calls, user Drive documents, or on-chain writes are made by these fixtures. The mocked pieces are engine/native history injection and cloud transport; runtime start/resume, encryption, storage, pagination, and mirror logic run as actual code.
20+
21+
## Review and integration status
22+
23+
The implementation and local regression suite are complete for this draft. The key suffix and per-process writer incarnation are the two deliberate details added to the proposed sketch: they prevent old-reader namespace collisions and concurrent processes sharing a device key.
24+
25+
The real Google Drive transport harness **passed** with two independent device processes on one physical Mac. It verified concurrent rollover, fresh readers, Korean offline writes and reconnect, pagination, unchanged sealed/legacy pages, and fixture cleanup. This is not physical two-device or normal-app UI acceptance; those remain unverified. The PR remains draft for that final acceptance review.
26+
27+
Known scope limits: old clients do not display new-format messages; two old clients still share legacy keys. Each process restart adds a chain head, increasing discovery reads. Delete does not add distributed tombstones, and a restored stale local backup is not automatically refreshed from its longer cloud copy. Serializing uploads does not provide server-side fencing for requests that complete after a timeout. This change does not recover history already overwritten before it was installed.
28+
29+
## Reproducible Drive transport acceptance
30+
31+
Run from the repository root after connecting Google Drive in AgentNet (the selected `AGENTNET_HOME` must contain its authorized Google setup):
32+
33+
```sh
34+
pnpm --filter @iqlabs-official/agent-sdk exec tsx test/test-gdrive-device-pages.ts
35+
```
36+
37+
The opt-in harness uses the production Drive adapter, two independent writer processes with distinct device IDs and local directories, plus fresh reader processes. It writes uniquely named encrypted fixtures under an unfunded test wallet's Drive folder. It asserts 92 messages after concurrent page rollover and 96 after Korean offline messages reconnect, compares paginated/full history, verifies one canonical session and unchanged sealed/legacy bytes, then deletes only its own page files. Existing OAuth configuration is referenced by symlink inside temporary profiles; credentials are never printed or copied into evidence. It does not start a model or send any blockchain transactions.
38+
39+
This is a two-device-process transport test on one physical host, not a claim of two physical devices or normal-app UI acceptance. The deliberate offline phase disables only each test process's transport. Folder discovery is initialized before simultaneous page writes; it does not test first-ever concurrent Drive folder creation.
40+
41+
To check the harness without Google access:
42+
43+
```sh
44+
pnpm --filter @iqlabs-official/agent-sdk exec tsx test/test-gdrive-device-pages.ts --local-smoke
45+
```
46+
47+
[Local harness receipt](drive-harness-local.json): **PASS**, including cleanup. This mode uses a filesystem cloud substitute and **is not Drive evidence**. [Real Google Drive receipt](drive-harness-live.json): **PASS**, including cleanup, on 2026-09-17 UTC at implementation commit `999fd2754061dcbcc00e8e47302338580bed8fd3`. OAuth connected successfully; the first request identified the project's disabled Drive API, and the test passed after enabling it. No credentials are included in the receipt.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
$ vitest run
2+
3+
RUN v4.1.8 /Users/nubs/Git/AgentNet-device-page-logs-20260917/packages/core
4+
5+
6+
Test Files 69 passed (69)
7+
Tests 503 passed | 5 skipped (508)
8+
Start at 22:43:04
9+
Duration 13.57s (transform 4.36s, setup 0ms, import 18.77s, tests 49.11s, environment 7ms)

‎docs/qa/cloud-resume/cli-build.log‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
$ tsup
2+
CLI Building entry: {"cli":"src/index.tsx"}
3+
CLI Using tsconfig: tsconfig.json
4+
CLI tsup v8.5.1
5+
CLI Using tsup config: /Users/nubs/Git/AgentNet-device-page-logs-20260917/surfaces/cli/tsup.config.ts
6+
CLI Target: node20
7+
CLI Cleaning output folder
8+
ESM Build start
9+
ESM dist/index.esm-4M5OBOX5.js 4.64 KB
10+
ESM dist/custom-ZCW7W72R.js 2.53 KB
11+
ESM dist/esm-AKO75YOG.js 27.42 KB
12+
ESM dist/skillSource-MS63SJO3.js 1.46 KB
13+
ESM dist/token2022-6N7MPBR3.js 1.39 KB
14+
ESM dist/icloud-UGPKS5K5.js 2.28 KB
15+
ESM dist/seed-TGT4FT4S.js 2.07 KB
16+
ESM dist/manual-F6NRSJXX.js 1.23 KB
17+
ESM dist/gdrive-IB5USRJ7.js 1.28 KB
18+
ESM dist/chunk-4RB4OWPY.js 2.74 KB
19+
ESM dist/chunk-EEOJZXOM.js 360.76 KB
20+
ESM dist/chunk-NO74MSXX.js 13.82 KB
21+
ESM dist/chunk-UQIJKSU2.js 3.10 KB
22+
ESM dist/chunk-QTXOJGHT.js 20.18 KB
23+
ESM dist/chunk-PILADXXM.js 3.97 KB
24+
ESM dist/chunk-XN5EYU52.js 3.56 KB
25+
ESM dist/chunk-AGWPZ3RE.js 1.34 MB
26+
ESM dist/chunk-UBBZAKR5.js 1.35 MB
27+
ESM dist/cli.js 1.57 MB
28+
ESM ⚡️ Build success in 213ms
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"transport": "Google Drive",
3+
"sessionId": "qa-device-pages-71395c44-b19e-4751-bda4-4f7336c2e28d",
4+
"physicalHosts": 1,
5+
"independentDeviceProcesses": 2,
6+
"result": "PASS",
7+
"onlineMessages": 92,
8+
"afterReconnectMessages": 96,
9+
"canonicalSessions": 1,
10+
"pagination": "matches full load",
11+
"sealedPages": "unchanged",
12+
"legacyPage": "unchanged",
13+
"cleanup": "verified",
14+
"implementationCommit": "999fd2754061dcbcc00e8e47302338580bed8fd3",
15+
"verifiedDateUTC": "2026-09-17"
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"transport": "local-files-NOT-Drive",
3+
"sessionId": "qa-device-pages-00a78f89-4ef3-4d9d-86c0-0bedb05a6d2c",
4+
"physicalHosts": 1,
5+
"independentDeviceProcesses": 2,
6+
"result": "PASS",
7+
"onlineMessages": 92,
8+
"afterReconnectMessages": 96,
9+
"canonicalSessions": 1,
10+
"pagination": "matches full load",
11+
"sealedPages": "unchanged",
12+
"legacyPage": "unchanged",
13+
"cleanup": "verified"
14+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
RUN v4.1.8 /Users/nubs/Git/AgentNet-device-page-logs-20260917/packages/core
3+
4+
5+
Test Files 2 passed (2)
6+
Tests 17 passed (17)
7+
Start at 22:41:16
8+
Duration 7.04s (transform 455ms, setup 0ms, import 1.22s, tests 7.44s, environment 0ms)

‎docs/qa/cloud-resume/mcp-build.log‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
$ tsup
2+
CLI Building entry: {"agentnet-mcp":"../core/src/mcp-stdio.ts"}
3+
CLI tsup v8.5.1
4+
CLI Using tsup config: /Users/nubs/Git/AgentNet-device-page-logs-20260917/packages/mcp/tsup.config.ts
5+
CLI Target: node18
6+
CLI Cleaning output folder
7+
CJS Build start
8+
CJS dist/agentnet-mcp.js 4.23 MB
9+
CJS ⚡️ Build success in 176ms
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Mac and physical Seeker QA — 2026-09-17 UTC
2+
3+
Base implementation: `1eadb451`; this follow-up changes only chat repaint reconciliation and tests.
4+
QA Android package: `com.iqlabs.agentnet.qa245`, isolated from the installed production app.
5+
Localhost bundle SHA-256: `68465bc83b86b7ec5a0402c5d79e0cd2763cae3a797c4a6b4661b2c19a2c4896`.
6+
7+
## Verified normal app behavior
8+
9+
- Mac and physical Seeker used the same approved wallet and Google Drive account.
10+
- Native Android Google authorization succeeded over Wi-Fi after fixing the phone's DNS. No application change was required for that network failure.
11+
- A real Mac Claude turn returned `MAC-245-READY`.
12+
- A real Seeker Codex turn returned `SEEKER-245-READY` after switching away from a quota-limited account.
13+
- The Mac opened the phone-created conversation from Drive and continued it with `MAC-245-CONTINUED`.
14+
- The still-open phone copy continued with `SEEKER-245-CONCURRENT`, without first loading the Mac's turn.
15+
- A fresh Mac view contained all six user/assistant messages in one canonical session, `01a0ad7b-37ee-7b22-8aef-91e3c0b4ee9f`.
16+
- Before this fix, restarting the phone and opening the conversation displayed only its four local messages, despite successful cloud sync. The newest timestamp matched, so reconciliation skipped the earlier Mac exchange.
17+
- After this fix, the restarted phone displayed all six messages, including the Mac exchange. See [physical Seeker screenshot](seeker-merged-history.png).
18+
19+
## Regression and build checks
20+
21+
- The two new same-timestamp regression cases fail against the previous implementation and pass with the fix. One fills the complete 30-message page; both include Korean text. A third case preserves newer local content against an older cloud result.
22+
- Core suite: 506 passed, 5 skipped, 69 files.
23+
- Localhost and webview builds passed. Isolated Android debug APK assembled successfully.
24+
- Existing live Drive harness separately covers pagination, rollover, offline tails/reconnect, immutable sealed pages and legacy preservation; see `drive-harness-live.json`.
25+
26+
## Boundaries
27+
28+
No blockchain posts or transactions were submitted. These were dedicated QA chat conversations. The physical test covers real two-device writes and restart; offline-tail and long-history checks remain the separate live-Drive harness, not claimed as physical-phone tests. Reused Android rootfs/native assets are development fixtures; this is not a production APK release certification. The unrelated browser OAuth popup correction remains in its separate worktree.
29+
30+
## Review follow-up: identical newest window, merged cursor
31+
32+
After Zo's review, the branch was rebased onto `1690b0ee` (merged #238 and #244).
33+
The added regression uses real encrypted SessionStore device chains over in-memory
34+
local/cloud adapters: B writes three older Korean messages, A writes 30 newer
35+
messages, and A reopens. The newest messages are identical locally and remotely,
36+
but only the merged cursor exposes B's older history. The test failed before the
37+
fix and now proves the chat dispatcher adopts that cursor and loads all three
38+
older messages without repainting the unchanged newest window.
39+
40+
Post-rebase checks: 547 core tests passed, 5 skipped; TypeScript passed; all six
41+
required Chromium panel tests passed; localhost and webview builds passed. This
42+
follow-up was not rerun on the sleeping physical phone; the device evidence above
43+
records the earlier six-message reconciliation test.
225 KB
Loading

0 commit comments

Comments
 (0)