Skip to content

feat: session grants give read access; share with the tenant via a grant (AGE-2577) - #1123

Merged
hary73 merged 12 commits into
mainfrom
haryahuja/age-2577-session-access-read-check
Oct 29, 2026
Merged

hary73 merged 12 commits into
mainfrom
haryahuja/age-2577-session-access-read-check

Conversation

@hary73

@hary73 hary73 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Second part of AGE-2577, on top of #1122 (merged). This completes the backend: grants are now the only way a session is shared.

  • Grants give read access to the session, its turns and its events.
  • "Share with everyone" is a tenant grant in access_control. Existing shared sessions are backfilled.
  • PATCH /sessions/{id} { shared } is deprecated: it now grants or revokes the tenant grant, so released UIs keep working. It is removed in the next release. Session.shared is still returned, derived from the tenant grant.

Behaviour by mode

  • TrueFoundry mode: share with specific users (picked through GET /users) and/or the whole tenant. Grants store servicefoundry's user id, which is the caller's subject.id, so grantees match.
  • Open source (standalone, OIDC): same as today. Only "share with everyone in the tenant" exists, and any tenant member can read a shared session. User grants return 422 and GET /users is empty. The new capability tells the UI to show only the tenant toggle.

API changes

  • SessionGrantSubjectType is user | tenant. A tenant subject's subject_id must be the caller's tenant id, otherwise 422. It skips the user lookup.
  • GET /api/v1/capabilities adds session_sharing: { enabled: boolean }: whether sessions can be shared with specific users (true only when a user source exists, i.e. TrueFoundry mode). Sharing with the tenant is always available.
  • UpdateSessionRequest.shared is marked deprecated. true adds the tenant grant, false removes it.
  • Session.shared is now read-only: true when the session has a tenant grant.

Changes

  • Store: IAccessControlStore exposes generic createAccessGrants / listAccessGrants / deleteAccessGrants (Postgres and SQLite). listAccessGrants filters by sessions and, optionally, subjects. The read rule and the tenant-shared lookup live in the API layer, not the store.
  • canReadSession checks the creator, then a grant, then agent managers (the only step that may call servicefoundry). A user grant only matches callers whose subject type is user, so service accounts and agents only match tenant grants.
  • Read call sites: get session, list session events, list turns, get turn, list turn events.
  • Still creator-only: PATCH, DELETE, cancel, create turn, subscribe, sandbox file download, grant management, schedules.
  • session.shared column is no longer read or written: removed from SessionRecord, UpdateSessionInput, the session stores and table types (core and backend). The column stays in the DB (DEFAULT false), unused.
  • Migration 20261028_000003_access_control_tenant_backfill (both backends): up inserts a tenant grant for every shared = true session, attributed to its creator, idempotent. down copies tenant grants back onto the column and removes them.

Deploy note

During a rolling deploy, old pods still read and write session.shared. A share toggled through an old pod after the migration won't be in the table. The next-release follow-up removes the PATCH { shared } alias and drops the column, re-running the backfill first, which closes that gap.

UI

Not changed here. The share dialog in trueforge-ui (and the version pinned by the platform frontend) keeps working through the deprecated alias. It moves to GET/POST /sessions/{id}/grants and the new capability separately.

How was this tested?

  • Contract cases (SQLite and Postgres): create, duplicate create, delete, session-delete cascade and tenant scoping. listAccessGrants is checked across several sessions (with ordering), with a subjects filter (type and id matched together), and with empty inputs.
  • Backfill migration, tested manually on SQLite and Postgres. I started main's server, created sessions A–D, and shared A and D through PATCH { shared } (B shared then unshared, C never shared). I then started this branch's server on the same database:
    • The migration ran at startup and wrote tenant grants for A and D only, attributed to their creator.
    • The API reported A and D as shared, and GET /grants listed the tenant.
    • The deprecated PATCH, POST /grants and the capability all worked. A user grant and another tenant's id returned 422.
    • Unsharing A left its legacy column true while the API reported false, confirming the column is no longer read.
    • A restart did not run the migration again.
  • Unit tests:
    • A granted user can use all five reads and gets 403 on every mutation and on grant management. Reads return 403 after revoke and for users without a grant. A service account with the same id as a granted user gets 403.
    • Tenant subject: works with the empty user store and lets any subject in the tenant read; another tenant's id returns 422; revoke removes access; shared is derived on get and list; the legacy column alone grants nothing; the deprecated PATCH { shared } grants and revokes the tenant and stays creator-only.
    • Capability: users is false with the empty store, true with the TrueFoundry store.
  • pnpm typecheck (backend and core), ESLint and Prettier on changed files, backend unit suite (748), core suite (534), both store suites.

Checklist

  • I have read the contributing guidelines
  • pnpm build, pnpm test, pnpm typecheck, pnpm lint:ci, and pnpm format:check pass locally
  • Tests added/updated where it makes sense
  • No hand-edits to generated code (packages/trueforge-sdk, python/trueforge_sdk, .github/fern/openapi/openapi.json, docs/openapi.json) — fork PRs omit SDK regen; maintainers regenerate after merge
  • Docs / .env.example updated if configuration or behavior changed

Note

High Risk
Changes authorization for session reads and migrates sharing state; incorrect grant checks or backfill could expose sessions or break existing shared links during rollout.

Overview
Session sharing moves off the stored session.shared flag onto access_control grants, which now enforce read access (session, turns, and events). User grants share with specific collaborators; tenant grants replace “share with everyone,” and wire Session.shared is computed from whether that tenant grant exists.

POST /sessions/{id}/grants accepts user and tenant subjects (tenant subject_id must match the caller’s tenant). The deprecated PATCH { shared } still toggles the tenant grant for older clients. GET /capabilities adds session_sharing.enabled (per-user sharing only when a user directory exists).

The session store no longer reads or writes shared; a migration backfills tenant grants for rows that were previously shared. Read paths use canReadSession + grant lookup; mutations and grant management stay creator-only.

Reviewed by Cursor Bugbot for commit 6677662. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6677662

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@truefoundry/trueforge Patch
@truefoundry/trueforge-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@hary73
hary73 marked this pull request as ready for review October 21, 2026 21:11
@hary73 hary73 changed the title feat: session grants give read access (AGE-2577) feat: session grants give read access and share-with-tenant moves to the access table (AGE-2577) Oct 21, 2026
Comment thread packages/trueforge/src/routes/sessionRoutes.ts Outdated
Comment thread packages/trueforge/src/routes/sessionRoutes.ts Outdated
Comment thread packages/trueforge/src/routes/sessionRoutes.ts Outdated
Comment thread packages/trueforge/src/db/accessControlEntryStore.ts Outdated
@hary73
hary73 force-pushed the haryahuja/age-2577-session-sharing-with-collaborators branch from 25826cd to 6c0cf64 Compare October 27, 2026 14:14
Base automatically changed from haryahuja/age-2577-session-sharing-with-collaborators to main October 28, 2026 04:43
@hary73
hary73 force-pushed the haryahuja/age-2577-session-access-read-check branch from 3bdc8bf to 49a6ed5 Compare October 28, 2026 05:16

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 49a6ed5. Configure here.

Comment thread packages/trueforge/src/schemas/session.ts
…ant (AGE-2577)

- Add `tenant` subject type; its subject_id must be the caller's tenant.
- canReadSession checks the creator, then a user or tenant grant, then agent managers.
- Session.shared is derived from the tenant grant.
- Remove PATCH { shared } and every read/write of session.shared; the column stays for a follow-up drop.
- Backfill shared sessions as tenant grants (20261027_000002_access_control_tenant_backfill).
- Add session_sharing.users capability so the UI can hide the user picker without a user source.

Co-authored-by: Cursor <cursoragent@cursor.com>
@hary73
hary73 force-pushed the haryahuja/age-2577-session-access-read-check branch from 49a6ed5 to 57ddb33 Compare October 28, 2026 05:29
@hary73 hary73 changed the title feat: session grants give read access and share-with-tenant moves to the access table (AGE-2577) feat: session grants give read access; share with the tenant via a grant (AGE-2577) Oct 28, 2026
Comment thread .github/fern/openapi/openapi.json Outdated
Comment thread .github/fern/openapi/openapi.json Outdated
Comment thread .github/fern/openapi/openapi.json Outdated
Comment thread packages/trueforge-core/src/agent-session/models/SessionRecord.ts
Comment thread packages/trueforge/src/apis/sessions.ts
Comment thread packages/trueforge/src/apis/sessions.ts
hary73 and others added 4 commits October 29, 2026 00:45
… (AGE-2577)

- IAccessControlStore exposes createAccessGrants / listAccessGrants / deleteAccessGrants;
  the user-or-tenant read rule and the tenant-shared lookup move to the API layer.
- Rename capability session_sharing.users to session_sharing.enabled.
- Mark PATCH shared deprecated through the OpenAPI flag with a short description.
- Keep the read-endpoint and PATCH route descriptions as on main.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@hary73
hary73 merged commit 4d0c0f9 into main Oct 29, 2026
17 checks passed
@hary73
hary73 deleted the haryahuja/age-2577-session-access-read-check branch October 29, 2026 05:59
truefoundry-saas added a commit that referenced this pull request Oct 29, 2026
* feat: session grants give read access; share with the tenant via a grant (AGE-2577) (#1123)

* feat: allow sharing sessions with collaborators (#1150)
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.

4 participants