Skip to content

feat: widgets read /v1/now with a token bound to a roster entry - #11

Closed
frahlg wants to merge 2 commits into
mainfrom
claude/cloud-token-now
Closed

frahlg wants to merge 2 commits into
mainfrom
claude/cloud-token-now

Conversation

@frahlg

@frahlg frahlg commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

What

The extension token and GET /v1/now from 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.

  • A device sends token.get on its app session and gets token {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/now with Authorization: Bearer <token> returns CBOR
    {envelopes, lastSeenMs}: the newest live, the newest live with
    sources, prices and hb, as the box signed them, in rising seq, and
    only what the device's scope allows.
  • The registry keeps the token's SHA-256 and the roster entry it is bound to
    (home, device id, epoch), never the token.
  • A read reaches the home only through store.For, with a new Token
    principal 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.
  • Every refusal is 401 with an empty body. The answer says no-store.
  • go/protocol moves to srcfl/ftw master 7c72a5b3, which holds
    cloudwire.MsgTokenGet, MsgToken and Token (feat(cloudwire): a watch reads FTW Cloud in one request ftw#1627).

The body of /v1/now has no Go type in cloudwire yet; this PR spells it in
internal/token. It belongs in cloudwire when the phone's widget reads it.

Tests

  • A token reads live, prices and hb as the box signed them, and never
    lp, plan, event, tiles or health (the protocol's promise).
  • A token stops reading when the roster drops its device, moves its epoch or
    runs out, when an owner suspends the device, when it expires, when a new
    token replaces it, and when the home is purged.
  • Bad headers, a wrong scheme and an unknown token all get the same 401.
  • token.get over a real app session returns a token that reads
    /v1/now over TLS 1.3, and the session goes on.
  • A suspension kills the token, and lifting it does not bring it back.
  • Store and registry tests for the principal, the scope and the rows.
  • TestNoRouteReadsAHome now runs against the real /v1/now.

Local: go vet, go test -race ./..., go test -tags ftwclouddev ./...,
scripts/check-imports.sh and the arm64 build all pass.

Notes

  • The service is now about 3,600 lines of Go without tests, over the
    3,000-line budget in the guide. It was at 3,240 before this change.
  • Nothing is deployed. The phone's widget does not ask for a token yet.

After review (914f7b9)

An independent review of the three stacked PRs found two defects here:

  • A suspension ended the suspended device's session only after dropping its
    token, so a registry error left the session open. The session now ends
    first.
  • A token.get 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 the access is gone.
    TokenPrune now matches the epoch, so it keeps a token the device got at
    its new epoch.

Each has a test.

🤖 Generated with Claude Code

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

chatgpt-codex-connector Bot commented Oct 14, 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-14T12:28:06.778879Z 077fe39 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: 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".

Comment thread internal/appsession/appsession.go Outdated
Comment on lines +539 to +540
if err := c.s.Tokens.Drop(c.home, d.ID); err != nil {
return err

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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>
frahlg added a commit that referenced this pull request Oct 14, 2026
…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>
frahlg added a commit that referenced this pull request Oct 14, 2026
…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>
@frahlg

frahlg commented Oct 14, 2026

Copy link
Copy Markdown
Member Author

Included in #13, merged as 341bd65.

@frahlg frahlg closed this Oct 14, 2026
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