fix(web): bound retained app documents by size; correct the cookie claim

Copilot review round 5 on #2100.

- The store bounded entry COUNT only, and the route admits 8Mi code units per
  document with a one-hour TTL — ~256Mi retainable on documents a server
  under test chooses the size of. Added a 64Mi retained-size budget evicted
  oldest-first alongside the count bound, with a running total kept in step
  with the map through a single `drop` seam so it cannot drift.
- The isolation claim was wrong about cookies. A separate port makes a
  separate ORIGIN, so DOM access, localStorage, sessionStorage and IndexedDB
  are isolated — but cookies are keyed by host and path, ignoring port, so an
  app served here shares the 127.0.0.1 jar with the Inspector and every other
  loopback service. Not a hole in the Inspector's own auth (the API token is
  a header plus a window global and sessionStorage, none of them cookies) and
  not closable without a distinct host this listener cannot mint, so it is
  stated plainly in both the module header and clients/web/README.md rather
  than left as a claim that is false for one of the three surfaces it names.
- Removed a shell-escaping artifact I left in the migration guide.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-24 01:06:12 -04:00 • committed by GitHub
commit 6011d4de03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -238,6 +238,30 @@ describe("createAppOriginController", () => {
expect((await fetch(first.url)).status).toBe(404);
});
it("evicts against a retained-size budget, not just the entry count", async () => {
// The count bound alone permits MAX_DOCUMENTS * 8Mi retained for an hour,
// on documents a server under test chooses the size of. Publish a few
// large ones — well under the 32-entry cap — and the earliest must stop
// being served.
const c = createAppOriginController({ port: 0, host: "127.0.0.1" });
controller = c;
const { url } = await c.start();
// 24Mi each: three fit under no count bound, but the third pushes total
// retained past the 64Mi budget and the first has to go.
const big = "x".repeat(24 * 1024 * 1024);
const first = c.publish({ html: big });
const second = c.publish({ html: big });
expect(first).not.toBeNull();
expect(second).not.toBeNull();
const third = c.publish({ html: big });
expect(third).not.toBeNull();
expect((await fetch(first!.url)).status).toBe(404);
// The newest is always served — its frame is about to fetch it.
expect((await fetch(third!.url)).status).toBe(200);
expect(url).toBe(c.getOrigin());
});
it("404s a document past its TTL", async () => {
// Fake `Date` ONLY — the TTL is the sole thing that reads it, and faking
// the timer queue too would deadlock the real HTTP round-trip below.