Skip to content

feat(agents): consent approval sets registry handle (#1800 slice 7)#1826

Closed
jaylfc wants to merge 2 commits into
feat/lead-identity-basefrom
feat/lead-identity-consent-handle
Closed

feat(agents): consent approval sets registry handle (#1800 slice 7)#1826
jaylfc wants to merge 2 commits into
feat/lead-identity-basefrom
feat/lead-identity-consent-handle

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 14, 2026

Copy link
Copy Markdown
Owner

slice 7 of #1800 identity epic, DO NOT MERGE

When an agent auth-request is approved, set the registry handle from the
sanitized identity claim (via _slugify). If the handle collides with an
ACTIVE identity, return 409 and leave the request PENDING so the approver
can pick a different variant. A SUSPENDED holder does not block reuse.

…entity claim

Derive the registry handle via _slugify from the already-sanitized identity
claim in the approve path. Before minting the canonical identity, check
get_by_handle(status='active'); if an active identity owns that handle,
return 409 and leave the request PENDING so the approver can try a different
variant. A SUSPENDED holder does not block reuse. After activating the new
agent, update its handle via the store update setter.

Tests:
- TestHandleSetOnApprove: handle is set, agent passes the a2a no-handle gate
- TestHandleCollisionActiveRejects: 409 + request stays pending when an
  active identity already has the handle
- TestHandleCollisionSuspendedAllowsReuse: approval succeeds once the
  previous holder is suspended

Docs-Reviewed: consent approve sets registry handle per lead-agent-identity-and-canvas-access.md
@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 →

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Important

Review skipped

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

🗂️ Base branches to auto review (2)
  • master
  • dev

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 31c69b72-0c4c-423c-a0a6-da2bfa7c1d4b

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/lead-identity-consent-handle

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.

@gitar-bot

gitar-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

# them to 'active' so they are NOT in the bus inactive/revocation feed and
# @taOSmd's identity-AND-grant gate accepts them.
await registry.set_status(canonical_id, "active", actor=user.user_id)
await registry.update(canonical_id, handle=handle)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Handle assignment is racy and not uniqueness-guaranteed

The 409 guard at line 341 runs before the agent is registered, so two concurrent approvals for the same (or slug-colliding) claim both pass the check, register with handle="", flip to active, and then both write the same handle here. The handle column has no UNIQUE constraint (agent_registry_store.py:43) and update() performs a blind UPDATE ... WHERE canonical_id = ? with no collision check, so the "one active identity per handle" invariant this feature relies on can be violated under concurrency.

Consider serializing the check->register->update (this store already uses a lock for exactly this check-then-write pattern, e.g. _reporting_lock) or adding a partial unique index WHERE handle <> '' plus a collision-checked update.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

_claim = record["identity_claim"].strip().removeprefix("@").strip()
display_name = _claim or record["framework"]

handle = _slugify(_claim)

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: Handle falls back to the literal "agent" when the claim is empty

_slugify(_claim) returns "agent" for an empty/whitespace claim (the or "agent" fallback inside _slugify), but display_name falls back to record["framework"]. So every approval with an empty identity_claim produces handle "agent", which collides with any previously activated "agent" identity and yields a confusing 409 ("pick a different identity_claim") even though the user supplied no real claim. Derive the handle from the same fallback so the two stay coherent.

Suggested change
handle = _slugify(_claim)
handle = _slugify(display_name)

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
tinyagentos/agent_registry_store.py 57 CREATE UNIQUE INDEX IF NOT EXISTS ux_agent_active_handle runs unconditionally in SCHEMA at init(); any existing DB already holding duplicate ACTIVE non-empty handles (possible under the prior unindexed version) will fail the index build with IntegrityError and abort init(), taking down the registry. Guard with a migration that clears duplicates first.

SUGGESTION

File Line Issue
tinyagentos/routes/agent_auth_requests.py 341 handle = _slugify(_claim) falls back to the literal "agent" for an empty claim while display_name falls back to the framework, so an empty claim yields handle "agent" that collides and produces a confusing 409. Derive the handle from the same fallback as display_name.
Files Reviewed (3 files)
  • tinyagentos/agent_registry_store.py - 1 issue (new)
  • tinyagentos/routes/agent_auth_requests.py - 1 issue (carried; prior WARNING on racy handle is now RESOLVED by the partial unique index + handle-at-birth + IntegrityError cleanup)
  • tests/test_routes_agent_auth_requests.py - 0 issues (concurrency/collision behavior is well covered)

Fix these issues in Kilo Cloud

Previous Review Summary (commit f5f0253)

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

Previous review (commit f5f0253)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
tinyagentos/routes/agent_auth_requests.py 365 Handle assignment is racy and not uniqueness-guaranteed: the 409 guard runs before registration, handle has no UNIQUE constraint, and update() does a blind write, so concurrent approvals can create duplicate active handles and break the one-active-identity-per-handle invariant.

SUGGESTION

File Line Issue
tinyagentos/routes/agent_auth_requests.py 340 Handle falls back to the literal "agent" for an empty claim while display_name falls back to the framework, causing confusing 409s; derive the handle from display_name for coherence.
Files Reviewed (2 files)
  • tinyagentos/routes/agent_auth_requests.py - 2 issues
  • tests/test_routes_agent_auth_requests.py - 0 issues (tests exercise the new behavior correctly)

Fix these issues in Kilo Cloud


Reviewed by hy3:free · Input: 97.2K · Output: 12.3K · Cached: 479.2K

…e orphan window

Register the handle at birth in the approve path so an agent is never left
ACTIVE with an empty handle, and add a partial unique index on
agent_registry(handle) WHERE status = 'active' AND handle != '' so the DB
atomically rejects a concurrent approval of the same identity_claim instead
of granting two active agents the identical handle (a2a "from" spoofing).
The concurrent loser now fails with 409 and its half-registered row is rolled
back and removed.

Docs-Reviewed: close consent handle-set TOCTOU + orphan window per lead-agent-identity-and-canvas-access.md
-- Pending / suspended / revoked rows and empty handles are excluded from the
-- index, so (a) a handle can be reused once its owner leaves 'active', and (b)
-- pre-existing empty-handle active agents never block the index's creation.
CREATE UNIQUE INDEX IF NOT EXISTS ux_agent_active_handle

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Unique index creation can abort init() on existing databases with duplicate active handles

This CREATE UNIQUE INDEX IF NOT EXISTS ux_agent_active_handle runs unconditionally inside SCHEMA via executescript at init() (base_store.py:38), and the WHERE status='active' AND handle!='' guard only excludes empty-handle rows. The earlier, unindexed version of this very feature (flagged by the prior review) could already persist two ACTIVE rows sharing a non-empty handle, so any database written in that window will fail the index build with IntegrityError and abort init(), taking down the whole registry store on upgrade.

Build this in a guarded migration that first detects/clears duplicate active handles (e.g. keep the earliest, rename or suspend the rest) before creating the index, so init() cannot fail on pre-existing bad data. The test in TestPartialUniqueHandleIndex only covers fresh DBs, not the upgrade path.

Minor note (route agent_auth_requests.py:381): the rollback() here is effectively a no-op because register() already committed the pending row (agent_registry_store.py:424) — the real cleanup is the delete() call. The surrounding comment is slightly inaccurate but harmless.

@jaylfc

jaylfc commented Jul 15, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #1838, which landed all seven slices as one consistent set on dev (with the CI-caught active-handle migration-order fix folded). Closing.

@jaylfc jaylfc closed this Jul 15, 2026
@jaylfc jaylfc deleted the feat/lead-identity-consent-handle branch July 15, 2026 23:08
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