Skip to content

[Cherry-pick] Cherry-pick to release-v0.182.0 - #1217

Merged
truefoundry-saas merged 2 commits into
release-v0.182.0from
cherry-pick/909501aa-b10d-410b-9680-498ca73a0fff/release-v0.182.0
Oct 29, 2026
Merged

truefoundry-saas merged 2 commits into
release-v0.182.0from
cherry-pick/909501aa-b10d-410b-9680-498ca73a0fff/release-v0.182.0

Conversation

@truefoundry-saas

@truefoundry-saas truefoundry-saas commented Oct 29, 2026 •

Copy link
Copy Markdown

Automated cherry-pick for release request 909501aa-b10d-410b-9680-498ca73a0fff.

Source PRs (merged to main):

Do not merge manually — this PR is merged automatically when the release request is approved. If you do merge it yourself, use "Sync status" on the request so it stops showing as pending.


Note

High Risk
Changes authorization for session reads and migrates sharing state; incorrect grant or migration logic could expose or hide session content.

Overview
Session sharing moves from a persisted shared flag on session rows to access_control grants: tenant-wide access is a tenant subject grant, and specific users get user grants with read access to the session, turns, and events. API responses still expose Session.shared, computed from the tenant grant; deprecated PATCH … { shared } now creates or deletes that tenant grant. A DB migration backfills existing shared sessions into tenant grants, and canReadSession authorizes reads via grant lookup (plus existing creator/agent-manager rules).

OpenAPI/SDK adds session_sharing.enabled on capabilities (true when the host has a user directory), extends grant subjects to include tenant, and documents grant semantics.

UI/runtime introduces an optional sessionAccess server port (list/grant/revoke + user search). The share dialog can invite collaborators when capabilities allow, lists owner and user grants (tenant grants stay on the tenant/private selector), and the Harness adapter wires grants and user APIs—filtering tenant grants out of the collaborator list.

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

@changeset-bot

changeset-bot Bot commented Oct 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2f76b29

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

This PR includes changesets to release 4 packages
Name Type
@truefoundry/trueforge-ui Patch
@truefoundry/trueforge Patch
@truefoundry/trueforge-core Patch
@truefoundry/trueforge-assistant-ui-runtime 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

export async function up(db: Kysely<unknown>): Promise<void> {
await sql`SET LOCAL lock_timeout = '5s'`.execute(db);
await sql`
INSERT INTO access_control (tenant_id, session_id, subject_type, subject_id, granted_by_subject, created_at)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium: Sharing revocations are lost during rolling upgrades

With the chart's default rolling update, this backfill runs while old replicas still serve requests: an owner's subsequent PATCH /api/v1/sessions/{id} with {"shared":false} handled by an old replica clears only session.shared, leaving the copied tenant grant intact. Any tenant member who knows the session ID can then continue reading its conversation and events through new replicas, even after the rollout finishes.

Synchronize legacy sharing writes with grants until all old writers are drained, or stop old writers before running the backfill.

@veria-ai

veria-ai Bot commented Oct 29, 2026

Copy link
Copy Markdown

PR overview

This PR cherry-picks changes into release-v0.182.0, including a PostgreSQL migration that backfills tenant access-control grants from existing session-sharing state.

One security issue remains open, with none addressed so far. During a default rolling upgrade, revoking session sharing through an old replica can leave a backfilled tenant grant intact. Tenant members who know the session ID can consequently continue reading its conversation and events through new replicas, even after the rollout finishes.

Open issues (1)

Fixed/addressed: 0 · PR risk: 7/10

@qodex-ai qodex-ai 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.

Inline findings below. See the walkthrough comment for the summary.

sessionId,
subjects: users.map(user => ({ subjectType: 'user', subjectId: user.id })),
});
setCollaborators(granted.filter(grant => grant.subjectType === 'user'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Major Invitation completion restores removed collaborators · bug

invite replaces collaborators with the grantAccess response even when removeCollaborator concurrently removes a collaborator. Successful revokeAccess completion never reconciles that replacement. A delayed invitation response restores the removed collaborator's row until another refresh. Refresh collaborators after all mutations finish, or serialize invitations and removals.

return false;
} finally {
collaboratorMutationsRef.current--;
collaboratorListEpochRef.current++;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Major Failed invitation loses pending collaborator refresh · bug

invite invalidates pending refreshCollaborators calls before grantAccess completes. Its failure path advances the epoch without fetching replacement collaborators. Existing collaborators can remain hidden until the user reopens the dialog. Retry refreshCollaborators when the last mutation finishes, including after grantAccess failure.

INSERT INTO access_control (tenant_id, session_id, subject_type, subject_id, granted_by_subject, created_at)
SELECT tenant_id, session_id, 'tenant', tenant_id, created_by_subject, now()
FROM session
WHERE shared

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Major Sharing divergence during rolling upgrades · security

The migration copies session.shared into tenant grants without ongoing synchronization. Older pods update session.shared, while setSharedWithTenant updates only grants. Revocations leave the other version's permission intact. Tenant members retain access after revocation through the other version. New pods also miss sharing changes older pods make after backfill. Synchronize session.shared and tenant grants throughout mixed-version deployment. Remove synchronization only after all older pods stop.

throw error;
}
if (body.shared !== undefined) {
await setSharedWithTenant(deps.accessControlStore, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Major Sharing revocations diverge during rolling upgrades · security

setSharedWithTenant changes tenant grants without updating session.shared. The migration backfills grants once, so older writers also leave grants stale. Older servers can authorize revoked sharing during rolling upgrades. Newer servers can retain access after older servers revoke sharing. Synchronize session.shared and tenant grants throughout the compatibility window, including writes from older servers.

@classmethod
def _missing_(cls, value: typing.Any) -> "SessionGrantSubjectType":
unknown = cls._UNKNOWN
unknown._value_ = value

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Minor Shared unknown enum value mutation · bug

missing overwrites the shared _UNKNOWN member's value for every unrecognized value. Parsing a second unknown subject type changes the first result's .value and loses its original subject type. Create a separate enum instance for each unknown value instead of mutating _UNKNOWN.

@qodex-ai

qodex-ai Bot commented Oct 29, 2026

Copy link
Copy Markdown

Qodex review

Reviewed units u1, u2, u3, u4, u5, u6, u7, u8. The changeset announces tenant collaborator filtering.

Effort: ●●●●● (5/5) · 5 findings: 4 major, 1 minor

Scope: the whole pull request (no previous review: the whole change is reviewed).

Qodex review threads: 5 open, 0 resolved by Qodex.

5 inline comments attached to specific lines below.

Walkthrough

Notes

  • Code around the change is partial: 487 symbols cut at the packet byte cap.
What Qodex checked

Lenses (openqodex review with checked answers, all dimensions)

  • Bugs and correctness
  • Security (auth, injection, SSRF, secrets, headers)
  • Performance regressions
  • Maintainability
  • Style and consistency

Scanners

  • actionlint did not run
  • bandit 1.9.4
  • brakeman did not run
  • cargo-deny did not run
  • checkov did not run
  • gitleaks 8.21.2
  • golangci did not run
  • hadolint did not run
  • kube-linter did not run
  • kubeconform did not run
  • osv-scanner 2.6.0
  • oxlint 1.86.0
  • rubocop did not run
  • ruff 0.8.4
  • semgrep 1.94.0
  • shellcheck did not run
  • sqlfluff did not run
  • sqllint did not run
  • squawk did not run
  • tflint did not run
  • trivy did not run
  • zizmor did not run
  • 6 scanner candidates raised or dropped by the reviewer

Quality filters

  • PR description: minimal (present: length; missing: checklist, issue link)
  • Per-category confidence floor (bug/security/performance 0.7, maintainability 0.71, style 0.75)
  • Severity threshold from .qodex.yaml: minor
  • Path excludes (none configured in .qodex.yaml)
  • Disabled rules (none configured in .qodex.yaml)

Verification (M1b probes against PR preview)

  • Preview deployment discovered (no successful deploy for this commit)
  • Probes (skipped, no preview to probe)

Cross-review dedup

  • Full review: no previous review: the whole change is reviewed
  • Carry-overs (no findings from earlier reviews still present)

Output

  • Walkthrough comment posted
  • 5 inline comments on specific lines

Reviewer: shown 303 of 303 changed hunks, opened 42 files (43 changed files never opened), 188 tool calls served, 106 model calls, 2 correction rounds, 314.7s. The library does not report tool calls asked apart from those it served, or a step cap.
Reviewed by Qodex · 379.1s

Every dimension is reviewed. Bugs, security, and performance post inline; low-severity style and convention notes are collected in the group above rather than dropped. Mark a finding resolved or flag a false positive in the Qodex Pull Requests view.

@truefoundry-saas
truefoundry-saas merged commit ae7c705 into release-v0.182.0 Oct 29, 2026
15 of 17 checks passed
@truefoundry-saas
truefoundry-saas deleted the cherry-pick/909501aa-b10d-410b-9680-498ca73a0fff/release-v0.182.0 branch October 29, 2026 11:27
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