Repository navigation
Conversation
A device asks for its extension token over its app session with token.get. GET /v1/now with that token returns the newest live, prices and hb as the box signed them, and nothing else. The registry keeps the token's SHA-256, never the token, bound to one roster entry. A read reaches the home only through store.For with a new Token principal, so the token stops working when the roster drops the device, moves its epoch or runs out, an owner suspends the device, or the home is purged. Moves the go/protocol pin to srcfl/ftw master 7c72a5b3, which holds cloudwire's token.get and token types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 077fe394ed
ℹ️ 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".
| if err := c.s.Tokens.Drop(c.home, d.ID); err != nil { | ||
| return err |
There was a problem hiding this comment.
Recheck sessions even when token deletion fails
When TokenDrop fails after registry.Suspend has already committed—for example, because of a transient SQLite I/O error—this return bypasses c.dev.Recheck(). The suspended device's existing app session therefore remains registered and continues receiving envelopes because delivery does not consult the suspension on each message; only the owner's session processing the suspension is closed. Ensure the session recheck still runs after a successful suspension even if token cleanup fails.
Useful? React with 👍 / 👎.
…evice's access From review: - suspend ended the suspended device's session only after dropping its token, so a registry error left the session open. Recheck now runs first. - A token.get that store.For admitted just before a suspension could store a token after the suspension dropped it. Issue now checks the device's access again once the row is stored, and drops the row if it is gone. - TokenPrune deleted by device id alone, so a token the device got at its new epoch meanwhile went too. It now matches the epoch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ssion gets 403 Merges claude/cloud-widget-push. Register now stores the app and trims the device's tokens of that kind. From review: token.get over the one-shot read answered 500 when Issue found the device shut out after admission; it is a refusal, 403. The README no longer says the service checks the watch role: it checks none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…at /v1/app/once (#11, #12, #13) One squash for three stacked PRs, ADR 0008 C6 and ADR 0009 step 7: - #11: the extension token (token.get, hashed, bound to a roster entry) and GET /v1/now. - #12: widget reloads through Apple when prices or a car change. - #13: a watch reads its home at /v1/app/once in one HTTPS request, Noise one-shot per the golden vectors. An independent review found no admission bypass, limit leak or logged secret; its smaller findings are fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What
The extension token and
GET /v1/nowfrom ADR 0008 (C6) and the protocol's"Extension token" section. ADR 0009 step 7 needs them first: a watch gets its
own token over the one-shot read.
token.geton its app session and getstoken {token, expiresInS}back. The token is 32 random bytes, base64url, valid 7 days.A device holds one; a new one replaces the old.
GET /v1/nowwithAuthorization: Bearer <token>returns CBOR{envelopes, lastSeenMs}: the newestlive, the newestlivewithsources,pricesandhb, as the box signed them, in risingseq, andonly what the device's scope allows.
(home, device id, epoch), never the token.
store.For, with a newTokenprincipal held to the same rule as a device: a roster in force that lists
that entry at that epoch, and no suspension. So the token stops working at
once when the roster drops the device, moves its epoch or runs out, an
owner suspends the device, or the home is purged. A roster also deletes the
rows it no longer allows, a suspension deletes the device's token, and
upkeep deletes expired rows.
no-store.go/protocolmoves to srcfl/ftw master 7c72a5b3, which holdscloudwire.MsgTokenGet,MsgTokenandToken(feat(cloudwire): a watch reads FTW Cloud in one request ftw#1627).The body of
/v1/nowhas no Go type incloudwireyet; this PR spells it ininternal/token. It belongs incloudwirewhen the phone's widget reads it.Tests
live,pricesandhbas the box signed them, and neverlp,plan,event,tilesorhealth(the protocol's promise).runs out, when an owner suspends the device, when it expires, when a new
token replaces it, and when the home is purged.
token.getover a real app session returns a token that reads/v1/nowover TLS 1.3, and the session goes on.TestNoRouteReadsAHomenow runs against the real/v1/now.Local:
go vet,go test -race ./...,go test -tags ftwclouddev ./...,scripts/check-imports.shand the arm64 build all pass.Notes
3,000-line budget in the guide. It was at 3,240 before this change.
After review (914f7b9)
An independent review of the three stacked PRs found two defects here:
token, so a registry error left the session open. The session now ends
first.
token.getadmitted just before a suspension could store a token afterthe suspension dropped it.
Issuenow checks the device's access againonce the row is stored, and drops the row if the access is gone.
TokenPrunenow matches the epoch, so it keeps a token the device got atits new epoch.
Each has a test.
🤖 Generated with Claude Code