Skip to content

feat: hold 48 hours of history tiles and answer tiles.get (C6) - #48

Merged
frahlg merged 7 commits into
mainfrom
feat/c6-tiles
Oct 23, 2026
Merged

frahlg merged 7 commits into
mainfrom
feat/c6-tiles

Conversation

@frahlg

@frahlg frahlg commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Step C6 of ADR 0008: the service keeps each home's last 48 hours of 5-minute history tiles and serves them to the home's phones, also while the box is away.

Paired with srcfl/ftw#1817 (wire format and box) and srcfl/ftw-app#98 (the app). go.mod pins go/protocol at srcfl/ftw b10a6939 on that branch. Move the pin to its merge commit before this merges.

What changes

  • Uplink: a signed tiles message is checked against the protocol's three shapes (cloudwire.Tiles.Check) and held as the box signed it.

    • A tile the box did not sign closes the uplink, as any other such message does.
    • A tile of no shape is dropped and counted (box_tile_of_no_shape); the uplink goes on.
    • hello_ok.have now names the closed tiles held, so a box that comes back sends only what is missing.
  • Store: tiles live in memory only, apart from the newest-of-each-class map.

    • Per home it keeps at most five closed tiles and 16 pieces of the filling tile. A whole piece replaces the pieces before it, and the closed tile replaces them all.
    • All homes together hold at most 256 MiB. Past that, new tiles are dropped and counted.
    • Each tile goes 48 hours after it ends, box online or not.
    • A tile from more than one tile ahead of the clock is refused.
    • Tiles are held only while the roster's grant serves a copy that includes ftw.history.read.
    • A withdrawn copy, a grant without history, DropCopy, a purge or the 90-day expiry deletes them.
  • App session: tiles.get {have} gets each closed tile the phone lacks at its etag, oldest first, then the pieces in the order the box sent them, then tiles.end {n}.

    • A device without ftw.history.read gets tiles.end {n: 0}.
    • Each new tile also goes to the sessions that may read it.
    • Message 2, /v1/app/once and /v1/now carry no tiles.
  • Dev route: /dev/held (ftwclouddev builds only) lists the tiles a home holds.

  • README: a section on tiles, and C6 leaves "Not built yet".

  • MCP: history_query still reads from the box only. The protocol says an agent never reads the readable copy, so every agent read is one the box audits and a revoke stops. While the box is away, the tool now says so plainly, for example: "The box is away. FTW Cloud last heard from it at 2026-10-23T14:25:31Z, 12 minutes ago. Its history is read from the box only, so ask again when it is back." It used to say only that the box is offline. The README says so too.

Open question for the owner: should history_query answer from these tiles while the box is away? That would change the rule that an agent never reads the copy, and the box would not see the read in its audit. It is not built.

Tests

  • go vet ./..., go test -race -count=1 ./..., go test -tags ftwclouddev ./... and scripts/check-imports.sh all pass. FuzzBoxMessage ran for 20 s.
  • internal/store, unit tests:
    • closed tiles and pieces are answered by etag and in order;
    • a whole piece and a closed tile replace what came before;
    • the per-home and total bounds hold;
    • tiles outlive the hour of silence and go after 48 hours;
    • each of the five ways to delete them works;
    • only a device with the scope reads tiles, and neither a snapshot nor a token carries them;
    • a new tile goes only to the sessions that may read it.
  • internal/uplink, with srcfl/ftw's signed vectors over a real TLS uplink:
    • the box's bytes are held, and have names the closed tile at reconnect;
    • a forged tile, or one signed by another key, closes the uplink and is never held;
    • a tile of no shape is dropped and the uplink stays;
    • a grant without history holds no tiles.
  • internal/mcp: with the box away, history_query says so and gives the time FTW Cloud last heard from it; agoWords.
  • internal/appsession, over a real Noise session:
    • tiles.get returns the vectors byte for byte;
    • with the vectors' have, only the pieces come back;
    • a viewer gets none;
    • tiles still come three hours after the box left;
    • tiles.end matches its vector.
  • End to end on a laptop: Core from the paired branch, built with a local endpoint and two simulated drivers, linked over QUIC to this branch's ftwcloudd. At connect the box sent the filling tile whole and the four closed tiles of the last 48 hours. They were empty, since that box's ledger was new. At 14:25:30 UTC it sent a one-point append for 14:20–14:25 with the measured values. A small checker read what the service held from /dev/held. Every tile verified with the box key and passed Check, and Lay rebuilt the filling tile through 14:25. Nothing touched the home box, the FTW Cloud host or AWS.

After review

  • Bytes are bounded:

    • an envelope over 4 KiB is dropped (box_tile_too_large);
    • Check takes only the four series;
    • a home holds at most 64 KiB of tiles (tile_home_full).

    So padded, validly signed tiles from a few boxes cannot fill the 256 MiB for every home.

  • A closed tile is replaced only by one the box sent later, by wallMs and then seq. A piece sent before the newest held piece is dropped (tile_older_than_held).

  • Sweep drops a home's tiles once its roster runs out. No phone can read them then, and a revoke made while the box was away cannot arrive.

  • The service sends resync {what: [tiles]}, at most once per 5 minutes, when a roster gives history back during a connection, and when room frees after a tile was dropped for lack of it.

  • New tests:

    • an agent's handle and session get no tiles;
    • a closed tile is replaced only by a later one;
    • a home's share of memory is bounded;
    • tiles go when the roster runs out;
    • an oversized envelope is dropped;
    • history back in the grant asks the box once.
  • go vet, go test -race ./..., the ftwclouddev run and the import check pass; FuzzBoxMessage ran 15 s.

🤖 Generated with Claude Code

frahlg and others added 2 commits October 23, 2026 16:10
A box with ftw.history.read in its grant sends tiles. The service checks
each against the protocol's three shapes and keeps it in memory as the
box signed it: five closed tiles and 16 pieces of the filling tile per
home, 256 MiB in all, each until 48 hours after it ends, box online or
not. hello_ok.have names the closed tiles held. A phone that may read
history asks with tiles.get {have}, gets what it lacks and tiles.end,
and gets each new tile as it comes. A revoke, a grant without history,
a purge or the 90-day expiry deletes them.

Pins go/protocol at srcfl/ftw 7dedcbf4 (feat/cloud-c6-tiles).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
So an end-to-end run can check what a real box sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T14:28:21.978778Z 59c9410 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59c9410677

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/store/tiles.go Outdated
Comment thread internal/store/tiles.go Outdated
frahlg and others added 5 commits October 23, 2026 16:32
An assistant reads history from the box only, never from FTW Cloud's
copy, so every read stays one the box audits and a revoke stops. While
the box is away, history_query now says so, with when FTW Cloud last
heard from it, instead of only that it is offline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…by net change

At the bound of 16 pieces the store dropped the oldest, which is the
whole piece the appends lay on, so a phone could no longer rebuild the
tile. It now refuses the extra append and keeps what lays back together;
the box's next whole piece carries it. The memory cap counted a tile's
full size before what it replaces, so near the cap it refused a whole
piece or a closed tile that would have freed bytes. It counts the net
change now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An envelope over 4 KiB is dropped, Check takes only the four series
  (go/protocol pinned at srcfl/ftw b10a6939), and a home holds at most
  64 KiB of tiles, so padded tiles from a few boxes cannot fill memory
  for every home.
- A closed tile is replaced only by one the box sent later, by wallMs
  then seq; a piece sent before the newest held piece is dropped.
- Tiles go when the roster runs out: no phone can read them then, and a
  revoke made while the box was away cannot arrive.
- The service sends resync {what: [tiles]}, at most once per 5 minutes,
  when a roster gives history back or room frees after a drop.
- Tests: an agent gets no tiles, the ordering, the home's share, roster
  expiry, an oversized envelope, and the resync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit ee5ecb3 into main Oct 23, 2026
2 of 3 checks passed
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.

1 participant