Skip to content

feat: security notices reach every owner's phone, copy or not, and the car class is held - #23

Merged
frahlg merged 2 commits into
mainfrom
claude/cloud-notices
Oct 16, 2026
Merged

frahlg merged 2 commits into
mainfrom
claude/cloud-notices

Conversation

@frahlg

@frahlg frahlg commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

What

Two things now reach the phones through the service. The box halves are srcfl/ftw#1661 (notices) and srcfl/ftw#1662 (the car class); the phone half is srcfl/ftw-app#77.

Security notices

Until now the uplink dropped a box's notice as an unknown class, so no owner heard of a new agent, an approval waiting, a new approval key, a new owner or a grant about to run out.

  • uplink: a signed notice goes to a new hook, OnNotice, with the readable copy on or off. The service holds none of it.
  • uplink: only once the home holds a roster, so a box that has sent none spends nothing. Not behind Serving(): that is the readable copy, and a notice must go with the copy off.
  • push.Notice: checks the body with cloudwire.ReadNotice (a 16-byte boot, kind, and on agent.added the client's name, nothing else), counts it against the home's notice cap as it comes, refuses a payload over APNs' 4 KB, then sends the box's envelope unchanged in an alert, through feat: a seam for shards, a quicker hot path, metrics, deploys without a storm, and a load harness #17's alert queue, with a 24-hour APNs expiry. A notice that is not one is dropped, counted (push_notice_not_a_notice) and logged. The aps is the same as every other alert's; the phone writes the words.
  • A suspended owner still gets notices. A suspension narrows events and widgets; it must not let one owner silence another before adding an agent, a key or an owner.
  • An event of a notice's kind is dropped (push_event_of_a_notice_kind): a box on #1646 sends one as both, and the event path reaches viewers.
  • Owners only. A notice goes to each owner phone registered for alerts and to no viewer and no watch token. An owner's phone may now register for alerts with the copy off, and a roster keeps that token. A viewer or a watch still needs ftw.events.read in its effective scope.
  • Events stay where they were. An event and box.unreachable go only to devices the newest roster lets read events (push.audience, which reads the signed roster the registry holds), so an owner registered for notices alone gets no event if one arrives.
  • Notices have their own hourly cap per home (20), so events cannot crowd them out. An alert with no device left reads no roster.
  • registry.PushPrune keys a registration by its app too, so an owner's watch-app token goes with events.

The car class

The pin moves to srcfl/ftw c41e6e98 (#1661's branch with master merged in, QUIC included), which carries master's car class (#1647). Rebased on main after #16 and #22; go mod tidy leaves no stray github.com/srcfl/ftw … +incompatible line in go.sum. The service holds car like the other copy classes, as the box sent it, and serves it under ftw.live.read. A token (/v1/now) never reads it.

Size

Non-test Go: internal/push +158 / −20 (1,167 lines now), internal/uplink +17 (1,035), internal/registry +8 / −7, cmd/ftwcloudd +1. About 157 lines net. The whole service is about 18,300 lines of non-test Go, well past the 3,000 in CLAUDE.md before this PR.

Deploy order

Cloud first (this), then boxes (srcfl/ftw#1661, #1662), then phones (srcfl/ftw-app#77).

Tests

  • push: an event of each notice kind is dropped and an ordinary one still goes (TestAnEventOfANoticeKindIsDropped); a suspended owner gets no event but every notice, and a notice expires in 24 h (TestASuspendedOwnerStillGetsNotices); an alert over 4 KB never queues. With the copy off, a notice gives one alert per owner phone and none to a viewer or a watch, who registered while the copy was on and lost their tokens with it; with the copy on, the same owners only; an event with the copy off goes to nobody (TestANoticeGoesToEachOwnerPhoneWithTheCopyOff). An owner's phone registers with the copy off; a viewer, a watch and an owner's watch app do not. A notice holding a reading, a source id or another kind, or an event's envelope, reaches nobody (TestANoticeCannotCarryReadings). Notices have their own cap. Three earlier tests now expect an owner to keep its alert token without events.
  • uplink: a notice reaches the hook unchanged with the copy off and the service holds nothing (TestANoticeReachesPushWithTheCopyOff); a notice before any roster goes nowhere; the car class is held and served under live, and not without it.
  • store: a token reads no car.
  • go vet ./... && go test -race ./..., go test -tags ftwclouddev ./... and scripts/check-imports.sh: green with -p 2.

Before merge

After #1661 merges, move the go/protocol pin to its merge commit and run go mod tidy.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 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-16T08:00:13.635431Z 0647def 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: 0647def7e2

ℹ️ 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/push/push.go
Comment on lines +440 to +441
return ok && known && d.Epoch == epoch && (getsNotices(d, kindName, app) ||
slices.Contains(d.Scopes, k.scope) && slices.Contains(r.Grant.Scopes, k.scope))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter alerts against the roster being applied

When a new roster removes ftw.events.read from an owner, this branch deliberately retains the owner's alert token for notices, but sendAlert determines event access by rereading the persisted roster. In uplink.roster, OnRoster runs before registry.SetRoster, so an alert worker running in that interval sees the old event-enabled roster and can send a queued event or box.unreachable notification after access was withdrawn. Retain the current roster in the push service for audience checks, or persist it before exposing the retained token.

Useful? React with 👍 / 👎.

… car class is held

A box's signed notice goes to the home's owner phones in an alert, as
the box signed it, with the readable copy on or off, and never to a
viewer or a watch. An owner's phone may register for alerts without the
copy for this, and a roster keeps that token. A notice holding anything
beyond its kind and, on agent.added, the client's name is dropped and
counted. Notices have their own hourly cap, counted as they come, reach
an owner another owner suspended, and Apple may hold them for a day.
Events and box.unreachable go only to devices the newest roster lets
read events, and an event of a notice's kind is dropped. An alert over
APNs' 4 KB never queues. A box that has sent no roster gets no notice
through.

The pin moves to srcfl/ftw c41e6e98 (#1661 with master, QUIC included),
which also carries master's car class: the service holds car like the
other copy classes and serves it under ftw.live.read; a token never
reads it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg force-pushed the claude/cloud-notices branch from 0647def to 304c9e4 Compare October 16, 2026 09:25
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit 70f06a3 into main Oct 16, 2026
2 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