Repository navigation
feat(access): let an assistant in for up to a year where the box takes it - #82
Conversation
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: 6f52d83c1a
ℹ️ 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".
| Section { | ||
| Picker(AccessWords.text("For"), selection: $flow.lengthMs) { | ||
| ForEach(flow.lengths, id: \.self) { Text(AccessWords.length($0)).tag($0) } | ||
| ForEach(flow.lengths(boxMs: access.boxLongestMs(offer.role)), id: \.self) { Text(AccessWords.length($0)).tag($0) } |
There was a problem hiding this comment.
Refresh box limits before capping the offer
When a consent link opens directly from RootView before Settings/Access has been visited, keysKnown is still false because OfferFlowView starts the offer without calling access.refresh(). Consequently boxLongestMs returns nil here, which is interpreted as an old box's 30-day limit, so the picker hides the 90-day and one-year choices even for a capable box. Approval also constructs the capped grant before AccessModel.signed performs its refresh, so the direct-link flow cannot grant more than 30 days; load agent.list before rendering or approving the offer.
Useful? React with 👍 / 👎.
Review of #82: a year granted on 17 Oct read "Until 17 Oct", which looks like today. "Until" and "Ends" now add the year when the end is in another calendar year or more than about five months off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s it The length choices are now 1, 7, 30 and 90 days and a year, and an offer starts at 30 days (a week for Sourceful), no longer at the longest asked. The app offers no more than the box's agent.list says it takes (longestMs, srcfl/ftw). A box from before that says nothing and takes 30 days for an assistant, so a newer app offers it 30 days at most, though FTW Cloud's offer asks for a year. Renewing follows the same rule. The demo box can play either kind of box; tests cover both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #82: a year granted on 17 Oct read "Until 17 Oct", which looks like today. "Until" and "Ends" now add the year when the end is in another calendar year or more than about five months off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #82: boxLongestMs is nil until agent.list has been read this visit, and a link opens the offer page with nothing else having read it. On that main path the picker and the grant stayed at 30 days. The offer page now reads agent.list when it appears, which also makes the "already has access" check in AccessModel.approve see the box's rows. OfferFlow.approve reads it first if the page has not, so a length chosen before the list arrived is not cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85c0457 to
52e77f9
Compare
Problem
Fredrik, 17 October 2026: an assistant's access should be able to last longer than 30 days, up to a year. srcfl/ftw#1686 raises the box's limit and FTW Cloud's offer now asks for a year (srcfl/ftw-cloud#27). An app must not offer a year to a box that refuses it: an old box answers
E_CAP_EXCEEDED; it does not cut the length short.Change
AgentListreadslongestMsfromagent.list.AccessModel.boxLongestMs(role)gives it only once the box has answered this visit; a cached list never counts.Grant.lengths,Grant.from,OfferFlow.lengths(boxMs:)and the renew picker cap at the least of the role's limit, the offer'sttlSand the box's word.AccessWire.unstatedLongestMs). A newer app on an older box offers 30 days at most, though the offer asks for a year.longestMslike a newer box, or none like an older one (SimulatedAccess.longestMs = nil), and checks the length on renew as well as on add.Apps already out (up to 0.4.7) cap at their own 30 days, so they never send a box more than it takes either.
agent.list, soboxLongestMsstayed nil and the picker and the grant stayed at 30 days. The page now reads the list when it appears, which also lets the "already has access" check inAccessModel.approvesee the box's rows.OfferFlow.approvereads it first if the page has not, so a length chosen before the list arrived is not cut.Order. Renewing offers a year from the box's
longestMsalone. With an FTW Cloud older than srcfl/ftw-cloud#27, a grant renewed for a year still loses its assistant 30 days after the code swap. Ship this after #27 is deployed.Tests
aYearOnlyWhereTheBoxSaysItTakesOne,anOfferStartsAt30Days,theBoxSaysHowLongItTakes(OfferTests).aLinkToABoxThatTakesAYearOffersAYearandapprovingReadsTheBoxFirst(AccessFlowTests): a link to a box that takes a year offers a year once the page has read the list, and grants it; a year chosen before the list arrived is kept. The second fails without the read inapprove.aFarEndShowsItsYear(OfferTests): a year out reads "Until 17 Oct 2027"; 30 days out has no year; another calendar year, or more than five months off, has one.aBoxThatTakesAYearIsOfferedAYearandanOlderBoxIsOfferedNoMoreThan30Days(AccessFlowTests, against the demo box): the old box gets 30 days and the grant goes through; a year sent anyway is refused.swift testinappleApp/FTWKit(rebased on main with iPhone: a consent link or QR code goes straight to review, no code to type #84): 511 and 257 tests pass. The app builds for the iOS Simulator (xcodebuild build).Pairs
AgentMaxMsa year,longestMsinagent.listand on the box page.ftw-webapp/contract/registry.yamlholds no copy of the number, andagent.list's fields live in srcfl/ftw'sdocs/agents.md.🤖 Generated with Claude Code