Skip to content

ci: cover the web e2e write path and seed the cargo caches from main - #1246

Draft
FSM1 wants to merge 6 commits into
mainfrom
ci/web-e2e-write-path-and-cargo-cache-seed
Draft

ci: cover the web e2e write path and seed the cargo caches from main#1246
FSM1 wants to merge 6 commits into
mainfrom
ci/web-e2e-write-path-and-cargo-cache-seed

Conversation

@FSM1

@FSM1 FSM1 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Held as a draft — blocked on UAT, which is blocked on #1253.
CI is green and the CodeRabbit verdict on the head is clean. The one outstanding
claim is whether an ordinary browser saves the file's bytes or the app shell
(#1247), and settling it needs a working login, which no method currently has.
Nothing in this PR depends on that check — the byte assertion goes through the
preview path, not the save affordance.

Closes #1096
Closes #1153

Two CI-owned slices in one PR: they share no file.

The web e2e write path

web-e2e.yml gains an ipfs/kubo service mirroring the contract job, plus the
two variables the write path needs:

  • KUBO_API_URL — without it KuboPinStore refuses every hosted upload with a
    503, and since PR fix(engine): settle a refused upload and isolate an unreadable sweep node #1217 the drain charges that as a spent attempt, so a write
    spec would dead-letter rather than fail on an assertion.
  • VITE_READ_ACCELERATOR_URL pointed at the same stack's gateway. This one is
    not optional either: the drain's rebase leg reads the record head block back
    before it publishes onto it, and with no content source configured the queue
    never drains at all. It is addressed as 127.0.0.1, not localhost, because
    Kubo serves localhost as a subdomain gateway and 301s every path request
    to <cid>.ipfs.localhost, which resolves nowhere in a browser.

Five new specs in tests/web-e2e/tests/write-path.spec.ts, bound to shipped
data-testids and shipped accessible names — folder create, rename, move and
delete, and an upload read back byte for byte through the preview. Two
deliberate shapes:

  • Every case ends on a drained queue with no dead letter. "The row is on
    screen" is the optimistic overlay and would pass over a write that never
    published.
  • Each assertion queues a probe folder behind the write under test. The op
    queue is strict FIFO, so the probe's pending mark clears only once everything
    ahead of it published — otherwise a delete or a move out takes its own row off
    the root and leaves the settle nothing to wait on.

The e2e project now takes every spec except the bundle-shape one, so a new
spec joins the gate the day it lands instead of when someone remembers to widen
a list. retries: 0 is untouched: the suite ran 78/78 green locally across six
repeats of the full file.

tests/web-e2e/README.md loses the "does not cover yet" section, which is now
false, and its local recipe gains Kubo and both variables.

Seeding the cargo caches from main

ci.yml triggers on pull_request only, and GitHub scopes a cache entry to the
ref that wrote it, so every PR's first cargo run was cold on every cargo job. A
run can read entries scoped to the default branch, so a new Cargo Cache Seed
workflow writes two of them on main-push and the PR jobs gain a trailing
restore-key that reaches them:

seed entry serves
main-cargo- linux-cargo-, core-kats-cargo-, client-browser-cargo-
main-wasm-cargo- wasm-engine-cargo-, web-e2e-cargo-

Two entries rather than one per job prefix, because the repo's cache budget is
already over the limit and these have to earn their bytes against the PR-scoped
entries LRU evicts. The keys hash Cargo.lock plus rust-toolchain.toml — the
two inputs that invalidate a dependency tree — so a merge that changes neither
hits its own key and writes nothing at all; the trigger is narrowed to the same
two files. A weekly cron and workflow_dispatch cover an entry evicted between
lockfile changes. macOS, Windows and the desktop matrix are deliberately not
seeded: they would add four more multi-GB entries on the most expensive runners.

Verification

  • pnpm lint:tracker-refs, pnpm typecheck, pnpm lint — all clean.
  • zizmor --no-online-audits .github/workflows/ .github/actions/ (the pinned
    1.25.2) — no findings; the new workflow is not added to any ignore list.
  • pnpm --filter @cipherbox/web-e2e test:e2e --repeat-each=6 against a live
    local stack — 78 passed.

Found on the way, not fixed here

Two runtime findings outside this PR's files, both reproducible:

  • Saving a file to disk silently writes the wrong bytes. useFileDownload
    mints a /stream/<ticket> URL and clicks an anchor at it; the Service Worker
    does not intercept that request, so the origin answers with the SPA fallback
    and the user gets a 394-byte index.html under the file's name, with no error
    surfaced. Reproduced 3/3. This is why the round trip asserts through the
    preview — the buffered read — rather than through the save.
  • A second forced refresh while a pass is in flight displaces it, and the
    queue drains on neither; polling the footer's refresh control at 1 Hz kept a
    single queued folder create unpublished for over 60 s, while one click settles
    it in ~1.8 s. The suite works around it by forcing exactly one pass and then
    waiting passively.

Also worth wiring by whoever owns apps/web: engineHostConfig never sets
profile, so a VITE_ENVIRONMENT=ci bundle runs the production 30 s poll
cadence rather than the CI profile's 1 s. That is what makes the forced-pass
workaround necessary at all.

Note

Add web E2E write-path tests and seed Cargo caches from main branch

  • Adds a new cargo-cache-seed.yml workflow that builds native and WASM targets on pushes to main and weekly, populating main-cargo- and main-wasm-cargo- cache keys as fallbacks for PR builds.
  • Updates CI and web-e2e workflows to fall back to these main-branch caches on cache misses, reducing cold-build times on PRs.
  • Adds a local IPFS (Kubo) service to web-e2e.yml with KUBO_API_URL and VITE_READ_ACCELERATOR_URL env vars wired for tests.
  • Adds a write-path.spec.ts E2E suite covering folder create/rename/move/delete and file upload, verifying content via UI preview and direct engine readback using a new window.__CIPHERBOX_ENGINE__.download introspection hook.
  • Expands the Playwright e2e project from only smoke.spec.ts to all tests except release-bundle.spec.ts.

Macroscope summarized 71e1d14.

Summary by CodeRabbit

  • New Features

    • Added support for creating, renaming, moving, and deleting folders and files.
    • Added file upload, preview, and download validation, including multiline and multibyte content.
    • Added hosted storage integration for upload and retrieval testing.
    • Added reliable file-content retrieval through the web engine.
  • Bug Fixes

    • Improved test reliability by waiting for vault updates and completed operations.
    • Expanded failure handling when storage services are unavailable.
  • Documentation

    • Updated web testing setup and documented supported file-management workflows.

The smoke slice gains an `ipfs/kubo` service, `KUBO_API_URL` and a
`VITE_READ_ACCELERATOR_URL` pointed at that stack's gateway, which is what
makes a write assertion possible: without a pin store the API refuses every
hosted upload with a 503, and without a content source the drain cannot read
back the state it publishes onto. The gateway is addressed as 127.0.0.1
because Kubo serves `localhost` as a subdomain gateway and 301s every path
request to a host that resolves nowhere.

On top of that: folder create, rename, move and delete specs plus an
upload read back byte for byte, all bound to shipped test ids. Each case ends
on a drained queue with no dead letter, and queues a probe folder behind the
write under test so a delete or a move out cannot settle vacuously. The e2e
project now takes every spec but the bundle-shape one, so a new spec is in the
gate the day it lands.

Separately, a `Cargo Cache Seed` workflow writes two cargo cache entries on
main, and the PR cargo jobs gain a trailing restore-key that reaches them.
`ci.yml` runs on `pull_request` only and GitHub scopes a cache entry to the ref
that wrote it, so every PR's first cargo run was cold.
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR seeds native and WASM Cargo caches from main, adds Kubo to the web E2E environment, exposes hexadecimal download introspection, and extends Playwright coverage to folder CRUD and upload/readback operations.

Changes

CI cache and web E2E write-path

Layer / File(s) Summary
Seed and restore Cargo caches
.github/workflows/cargo-cache-seed.yml, .github/workflows/ci.yml
A scheduled workflow seeds native and WASM Cargo caches. CI jobs restore from job-specific prefixes or shared main prefixes.
Provide hosted E2E services
.github/workflows/web-e2e.yml, tests/web-e2e/README.md, tests/web-e2e/playwright.config.ts
The E2E workflow starts Kubo and configures pinning, gateway, and WASM cache settings. Documentation covers local Kubo setup and required environment variables. The Playwright project excludes only the release-bundle test.
Exercise file mutations and upload readback
apps/web/src/engine/introspection.ts, apps/web/src/engine/introspection.test.ts, tests/web-e2e/page-objects/files.page.ts, tests/web-e2e/page-objects/vault.page.ts, tests/web-e2e/tests/write-path.spec.ts
The introspection API supports hexadecimal downloads. Page objects support folder creation, rename, move, deletion, upload, preview, settlement, and readback. Tests verify publication, dead-letter state, and exact multiline multibyte content preservation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Playwright
  participant FilesPage
  participant WebAPI
  participant Kubo
  participant VaultPage
  Playwright->>FilesPage: perform folder or upload action
  FilesPage->>WebAPI: submit file operation
  WebAPI->>Kubo: store hosted content
  VaultPage->>WebAPI: settle and read uploaded content
  VaultPage-->>Playwright: return decoded file bytes
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements write-path E2E coverage and Kubo setup [#1096], plus main-scoped native/WASM Cargo cache seeding with lockfile and toolchain invalidation [#1153].
Out of Scope Changes check ✅ Passed The workflow, documentation, E2E, and introspection changes directly support the linked write-path and Cargo cache objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: web E2E write-path coverage and Cargo cache seeding from main.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/web-e2e-write-path-and-cargo-cache-seed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@FSM1
FSM1 marked this pull request as ready for review August 10, 2026 21:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/web-e2e/page-objects/files.page.ts`:
- Around line 91-97: The FilesPage.preview() path only validates decoded text,
so add a download helper in tests/web-e2e/page-objects/files.page.ts that
returns the raw facade.download() bytes as Uint8Array. In
tests/web-e2e/tests/write-path.spec.ts, replace the upload round-trip text
assertion with byte-for-byte comparison against a Uint8Array fixture, retaining
Buffer conversion only where Playwright upload requires it.
- Around line 84-89: Update FilesPage.upload() in
tests/web-e2e/page-objects/files.page.ts to accept Uint8Array and pass
Buffer.from(bytes) as the Playwright buffer. Update its caller in
tests/web-e2e/tests/write-path.spec.ts to provide a Uint8Array, preserving the
existing upload behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9df815b7-e29e-40b9-9f2d-64fae54f7fa8

📥 Commits

Reviewing files that changed from the base of the PR and between 4128ccc and f927e01.

📒 Files selected for processing (8)
  • .github/workflows/cargo-cache-seed.yml
  • .github/workflows/ci.yml
  • .github/workflows/web-e2e.yml
  • tests/web-e2e/README.md
  • tests/web-e2e/page-objects/files.page.ts
  • tests/web-e2e/page-objects/vault.page.ts
  • tests/web-e2e/playwright.config.ts
  • tests/web-e2e/tests/write-path.spec.ts

Comment thread tests/web-e2e/page-objects/files.page.ts
Comment thread tests/web-e2e/page-objects/files.page.ts
@FSM1
FSM1 marked this pull request as draft August 10, 2026 22:04
FSM1 and others added 4 commits August 10, 2026 22:07
The round-trip test is named for bytes and asserted decoded text. The
preview renders through a fatal UTF-8 decode that folds a BOM, so a byte
the round trip changed and the decoder swallowed left the assertion
green.

FilesPage.download saves the file the way a member would and returns
what reached disk, and the test compares that with the uploaded bytes.
The preview assertion stays: it proves the read path renders, which the
saved bytes do not.

upload takes Uint8Array now, converting to Playwright's Buffer payload
at the boundary rather than making every caller hold one.
The byte assertion timed out: preview leaves its dialog open, and the
modal backdrop intercepts every click meant for a row, so the download
action was never reached and no download event fired.

preview closes the dialog it opened and waits for it to go, which is
what a caller reading a file back then acting on the listing needs.
@FSM1
FSM1 marked this pull request as ready for review August 11, 2026 00:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/web/src/engine/introspection.test.ts (1)

77-87: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add invalid-input coverage for nodeHex.

The test covers only successful conversion. Add cases for odd-length and non-hex values. Assert that the bridge rejects with TypeError before client.facade.download is called.

As per path instructions, tests under **/*.{test,itest}.ts must cover edge cases and assert behavior. The supplied fromHex contract defines these failure cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/engine/introspection.test.ts` around lines 77 - 87, Add
invalid-input tests for the `window.__CIPHERBOX_ENGINE__?.download` bridge
covering odd-length and non-hex `nodeHex` values. Assert each call rejects with
`TypeError` and verify `client.facade.download` is not called, while preserving
the existing successful conversion test.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/web/src/engine/introspection.test.ts`:
- Around line 77-87: Add invalid-input tests for the
`window.__CIPHERBOX_ENGINE__?.download` bridge covering odd-length and non-hex
`nodeHex` values. Assert each call rejects with `TypeError` and verify
`client.facade.download` is not called, while preserving the existing successful
conversion test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e0306476-6a56-4e99-b630-86090d9b2524

📥 Commits

Reviewing files that changed from the base of the PR and between f927e01 and 8ed3d76.

📒 Files selected for processing (5)
  • apps/web/src/engine/introspection.test.ts
  • apps/web/src/engine/introspection.ts
  • tests/web-e2e/page-objects/files.page.ts
  • tests/web-e2e/page-objects/vault.page.ts
  • tests/web-e2e/tests/write-path.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/web-e2e/tests/write-path.spec.ts
  • tests/web-e2e/page-objects/files.page.ts

FSM1 commented Aug 11, 2026

Copy link
Copy Markdown
Owner Author

Review disposition — CodeRabbit on 8ed3d76

0 actionable, 1 nitpick. The nitpick creates no thread, so it is dispositioned here. Fixed in 71e1d14.

Nitpick (1 of 1)

Finding Disposition
introspection.test.ts covers only the successful nodeHex conversion — add odd-length and non-hex cases, asserting rejection before facade.download is called Taken. The contract checks out: fromHex throws TypeError on an odd length and again on a non-hex character, and the tap is async, so both surface as rejections.
it.each(['010', 'zz'])('refuses %s as a node id, before the engine is asked', async (bad) => {

The expect(download).not.toHaveBeenCalled() half is the part worth having. It pins the ordering — decode first, ask the engine second — which is the only thing about this bridge a reader could get wrong. 'zz' rather than an uppercase pair, because fromHex accepts A-F.

Negative-controlled: moving client.facade.download(…) ahead of the fromHex call fails all three download cases. Reverted; the tap is byte-identical to 8ed3d76.

Earlier findings

Both actionable findings from the f927e01 review are resolved, and the second one is resolved differently from what my first reply said — see that thread. The saved-bytes route failed Web E2E three times; the third failure showed the browser saving the app's own index.html, because the /stream/<ticket> request reached the origin server instead of the Service Worker. The byte assertion now runs through an engine tap, and whether that Service Worker bypass reaches real browsers is left open rather than settled here.

Gates

Gate Result
pnpm typecheck 0
pnpm lint 0
pnpm lint:tracker-refs 0
apps/web unit suite 12/12 in introspection.test.ts
Web E2E Smoke on 8ed3d76 success

Web E2E is the gate that matters for this PR and it is green on the head this nitpick was raised against; 71e1d14 adds a unit test only.


Generated by Claude Code

FSM1 commented Aug 11, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

71e1d14 landed inside the rate-limit window after your review of 8ed3d76 and no review followed once the window cleared, so the current head carries no verdict. The delta is one file — the invalid-nodeHex cases in apps/web/src/engine/introspection.test.ts your nitpick asked for.


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

@FSM1: I will review the current head, including the invalid nodeHex test cases in apps/web/src/engine/introspection.test.ts.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@FSM1
FSM1 marked this pull request as draft August 11, 2026 21:30
@FSM1

FSM1 commented Aug 11, 2026

Copy link
Copy Markdown
Owner Author

Held as a draft: blocked on UAT, which is blocked on #1253.

CI is green and CodeRabbit's verdict on 71e1d1405 is clean. What is outstanding is not a code review — it is the one claim in this PR that only a human at a browser can settle, and settling it needs a working login.

What is unverified

Whether an ordinary browser saves the file's own bytes or the app shell. useFileDownload mints a /stream/<ticket> URL and clicks an anchor; if the Service Worker does not intercept that request, the origin answers it with the SPA fallback and the member receives a 394-byte index.html under the file's name, with no error surfaced. Filed as #1247.

It cannot be settled inside Playwright, because Playwright's own CDP download capture is the suspect. It needs a real browser, a real session, DevTools open, and a check that the worker is controlling the page before the click — if it is not, MediaService.streaming is false, streamTicket returns null, and save quietly takes the buffered blob path, which works correctly and proves nothing about the path under suspicion.

Why it is blocked

Login is down on every method. Google returns 401 invalid_client because the Web3Auth project client ID is passed where the provider's belongs; wallet dead-ends at NotStarted; passwordless email is Web3Auth's rather than ours. All three are #1253, and the Google half specifically is #1256 — the one piece that is independent of the ADR and can land on its own.

What this PR does not depend on

Nothing here rests on that verification. The round trip asserts through the preview path (buffered facade.download) rather than the save affordance, precisely because the save path is under suspicion — so the byte assertion never touches it. #1247 records that the workaround should be removed once the save path is trusted.

Flip back to ready once a session can be created and the check is run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: seed the cargo caches from main so a PR's first run is not cache-cold ci: extend the web e2e smoke slice to the write path

2 participants