Skip to content

feat(invite): project_invites store + mint/list/revoke routes (#1780 S1)#1855

Merged
jaylfc merged 2 commits into
devfrom
feat/invite-s1-store
Jul 16, 2026
Merged

feat(invite): project_invites store + mint/list/revoke routes (#1780 S1)#1855
jaylfc merged 2 commits into
devfrom
feat/invite-s1-store

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Slice S1 of the external-agent invite feature.

Implements the ProjectInviteStore with mint/get/list_for_project/revoke/redeem, the admin-gated POST/GET/DELETE routes under /api/projects/{project_id}/invites, and wires the store + router into app.py.

Tests cover: 6-digit invite_id + 4-digit PIN, project_tasks always included, 10-pending cap -> 429, wrong-pin attempts + 5th invalidates, correct-pin single-use (2nd redeem raises), expired invite, revoke, list omits pin_hash, and a boot-migration test (seed a project_invites table missing redeemed_request_id, assert init() boots).

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 59ab48f6-3a8f-4c77-8e2d-044a26c7b03a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/invite-s1-store

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@gitar-bot

gitar-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

@jaylfc jaylfc enabled auto-merge (squash) July 16, 2026 18:20
)
row = await cursor.fetchone()
pending_count = row[0] if row else 0
if pending_count >= _PENDING_CAP:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Pending-cap and list queries key on project_id = ?, but project_id is now nullable (commit 2). Invites minted with project_id=None will never be counted toward _PENDING_CAP (SQLite treats NULL = NULL as false) and won't be returned by list_for_project(None). If agent-centric invites with no project are ever minted through this store, the 10-pending cap becomes a no-op for them and they silently disappear from project listings.

Consider keying the cap count on a coalesced/project-scoped condition and deciding explicitly whether NULL-project invites should participate in the cap.

await self._db.commit()

def _generate_invite_id(self) -> str:
return f"{secrets.randbelow(1_000_000):06d}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: invite_id is a 6-digit random number with no uniqueness/collision retry before the INSERT. With a PRIMARY KEY on invite_id, a collision raises an unhandled IntegrityError, surfacing as a 500. The probability is low but non-zero, especially under high mint volume or after the id space is exhausted.

Consider looping a few times to regenerate invite_id on IntegrityError (or widening the id space).

raise InviteExpiredError("invite has expired")

attempts = row["redeem_attempts"] or 0
if attempts >= _MAX_ATTEMPTS:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Once redeem_attempts reaches _MAX_ATTEMPTS, a subsequent call (even with the correct PIN) falls through line 229 and raises InvitePinError("invalid invite id or pin"). The invite is actually locked/expired, so this error message is misleading to callers and masks the real state.

Consider checking expired/locked status first (or raising a distinct InviteLockedError/InviteExpiredError) so the response reflects that the invite is exhausted rather than that the PIN was wrong.

await s.close()


def _make_scopes(*extra):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: _make_scopes is defined but never used anywhere in this test module. It appears to be dead test code (the store itself already forces project_tasks in mint).

Remove it to avoid confusion, or wire it into a test if it was intended to assert the allowed-scope ordering.

@kilo-code-bot

kilo-code-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 3
Issue Details (click to expand)

SUGGESTION

File Line Issue
tinyagentos/projects/invite_store.py 117 Pending cap and list_for_project still key on project_id = ?; NULL project_id invites (now explicitly allowed via the nullable schema/mint default) bypass the cap and are never listed
tinyagentos/projects/invite_store.py 97 6-digit invite_id generated without collision retry; PRIMARY KEY clash throws unhandled IntegrityError
tinyagentos/projects/invite_store.py 229 After redeem_attempts reaches _MAX_ATTEMPTS, a correct PIN still raises a misleading InvitePinError instead of a locked/expired error
Files Reviewed (1 files in incremental diff)
  • tinyagentos/projects/invite_store.py - 3 issues

Incremental note: commit a58179e made project_id nullable (schema + mint default) but did not address the three prior findings. The nullable change makes the pending-cap/list project_id = ? gap (line 117) more acute rather than resolving it.

Fix these issues in Kilo Cloud

Previous Review Summary (commit a1524b2)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit a1524b2)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 4
Issue Details (click to expand)

SUGGESTION

File Line Issue
tinyagentos/projects/invite_store.py 113 Pending cap uses project_id = ?; NULL project_id invites (now allowed) bypass the cap and list_for_project
tinyagentos/projects/invite_store.py 97 6-digit invite_id generated without collision retry; PRIMARY KEY clash throws unhandled IntegrityError
tinyagentos/projects/invite_store.py 229 After attempts exhausted, correct PIN still raises misleading InvitePinError instead of a locked/expired error
tests/projects/test_invite_store.py 26 _make_scopes helper defined but never used (dead test code)
Files Reviewed (7 files)
  • tinyagentos/projects/invite_store.py - 3 issues
  • tinyagentos/routes/project_invites.py - 0 issues
  • tinyagentos/app.py - 0 issues
  • tinyagentos/routes/__init__.py - 0 issues
  • tests/conftest.py - 0 issues
  • tests/projects/test_invite_store.py - 1 issue
  • tests/test_routes_project_invites.py - 0 issues

Fix these issues in Kilo Cloud


Reviewed by hy3:free · Input: 39.9K · Output: 3.8K · Cached: 193.8K

An Agents-app invite registers an agent with no project bound; SQLite cannot
drop NOT NULL via ALTER later, so relax it before the store ships.

Docs-Reviewed: invite mint/list/revoke are admin-only project-management routes (UI-consumed, not part of the agent-JWT surface); the agent-facing contract is the S2 redeem route + connection bundle, specified in docs/design/external-agent-project-invite.md
@jaylfc jaylfc force-pushed the feat/invite-s1-store branch from a1524b2 to a58179e Compare July 16, 2026 18:53
@jaylfc jaylfc merged commit a60ba3a into dev Jul 16, 2026
9 checks 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