Skip to content

feat(access): let an assistant in for up to a year where the box takes it - #82

Merged
frahlg merged 3 commits into
mainfrom
claude/agent-grant-year
Oct 17, 2026
Merged

frahlg merged 3 commits into
mainfrom
claude/agent-grant-year

Conversation

@frahlg

@frahlg frahlg commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

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

  • Choices (Face ID approval and renew): 1, 7, 30 and 90 days and a year for an assistant. Sourceful stays at 1, 3, 7 and 14 days. A year reads "1 year" / "1 år".
  • An offer starts at 30 days (a week for Sourceful). Before, it started at the longest choice, which would now be a year.
  • The box decides the most. AgentList reads longestMs from agent.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's ttlS and the box's word.
  • A box that says nothing takes 30 days (AccessWire.unstatedLongestMs). A newer app on an older box offers 30 days at most, though the offer asks for a year.
  • The demo box sends longestMs like 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.

  • The offer page reads the box. A link (iPhone: a consent link or QR code goes straight to review, no code to type #84) opens the offer page with nothing else having read agent.list, so boxLongestMs stayed 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 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.
  • A far end shows its year. "Until" and "Ends" add the year when the end is in another calendar year or more than about five months off, so a year granted on 17 Oct no longer reads "Until 17 Oct".

Order. Renewing offers a year from the box's longestMs alone. 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).
  • aLinkToABoxThatTakesAYearOffersAYear and approvingReadsTheBoxFirst (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 in approve.
  • 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.
  • aBoxThatTakesAYearIsOfferedAYear and anOlderBoxIsOfferedNoMoreThan30Days (AccessFlowTests, against the demo box): the old box gets 30 days and the grant goes through; a year sent anyway is refused.
  • swift test in appleApp/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).
  • Not seen in the simulator: CoreSimulator on this Mac is older than the Xcode build ("Current version (1171.7.0) is older than build version (1174.9.2)"), so no simulator runs here. The only visible change is the extra items in two pickers and the 30-day start; the Apple workflow photographs the demo.

Pairs

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 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-17T06:39:02.608364Z 6f52d83 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: 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) }

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 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 👍 / 👎.

frahlg added a commit that referenced this pull request Oct 17, 2026
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>
frahlg and others added 3 commits October 17, 2026 11:55
…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>
@frahlg
frahlg force-pushed the claude/agent-grant-year branch from 85c0457 to 52e77f9 Compare October 17, 2026 09:56
@frahlg
frahlg merged commit d797adb into main Oct 17, 2026
1 check 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