Skip to content

tsk-2wbp3f [OPEN] Delegation A3: deliver the minted invite to the re - #2199

Closed
jaylfc wants to merge 1 commit into
devfrom
exec/tsk-2wbp3f
Closed

tsk-2wbp3f [OPEN] Delegation A3: deliver the minted invite to the re#2199
jaylfc wants to merge 1 commit into
devfrom
exec/tsk-2wbp3f

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Autonomous build of board card tsk-2wbp3f.

Files:
tinyagentos/delegation_handler.py | 537 ++++++++++++++++++++++++++++++
tinyagentos/projects/invite_store.py | 61 ++--
tinyagentos/projects/project_store.py | 58 ++++
tinyagentos/routes/agent_auth_requests.py | 10 +
tinyagentos/routes/decisions.py | 62 +++-
tinyagentos/routes/peer.py | 20 +-
tinyagentos/routes/project_invites.py | 20 ++
9 files changed, 1124 insertions(+), 48 deletions(-)

Summary by CodeRabbit

  • New Features

    • Added cross-user collaboration delegation for sponsored agents.
    • Supports automatic or human-approved delegation requests with scope validation.
    • Added sponsor tracking for delegated agents and project membership/settings management.
    • Added delegation revocation, task unassignment, contact-level shutdown, and instance-level emergency disable controls.
    • Invite metadata now persists and carries sponsorship details through redemption.
  • Bug Fixes

    • Peer inbox requests now recognize delegation messages and return appropriate status responses.
    • Delegation approval and denial messages are routed to the correct agent.

@gitar-bot

gitar-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds cross-user sponsored-agent delegation with scope and envelope validation, automatic or decision-based invite issuance, sponsor metadata persistence, approval propagation, sponsor-scoped registry APIs, revocation cascading, task unassignment, and peer-route kill switches.

Changes

Sponsored delegation flow

Layer / File(s) Summary
Sponsorship and invite persistence
tinyagentos/agent_registry_store.py, tinyagentos/projects/invite_store.py, tinyagentos/projects/project_store.py, tests/test_collab_d1_delegation.py
Agent records store sponsor contacts, invites persist JSON metadata, project membership/settings APIs are added, and persistence behavior is tested.
Delegation request processing
tinyagentos/delegation_handler.py, tinyagentos/routes/peer.py, tests/test_collab_d1_delegation.py
Peer inbox delegation requests validate envelopes and scopes, verify project membership, then mint invites automatically or create approval decisions.
Approval and sponsor propagation
tinyagentos/routes/decisions.py, tinyagentos/routes/project_invites.py, tinyagentos/routes/agent_auth_requests.py
Delegation decisions route approval results, and sponsor metadata flows through invite redemption into reused or newly registered identities.
Revocation and peer shutdown
tinyagentos/delegation_handler.py, tinyagentos/routes/peer.py
Sponsored identities can be revoked with task unassignment and system messaging, while contact-level and instance-level peer shutdown controls are added.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DelegatingAgent
  participant PeerInbox
  participant DelegationHandler
  participant ProjectStore
  participant InviteStore
  participant DecisionStore
  participant InviteRedeemer
  participant AgentRegistry
  DelegatingAgent->>PeerInbox: delegation_request
  PeerInbox->>DelegationHandler: dispatch envelope
  DelegationHandler->>ProjectStore: check membership and auto-approval setting
  alt auto approval
    DelegationHandler->>InviteStore: mint invite with sponsor metadata
  else manual approval
    DelegationHandler->>DecisionStore: create delegation gate
    DecisionStore->>DelegationHandler: approved decision
    DelegationHandler->>InviteStore: mint sponsored invite
  end
  InviteRedeemer->>InviteStore: redeem invite
  InviteRedeemer->>AgentRegistry: register or update sponsored identity
Loading

Possibly related PRs

  • jaylfc/taOS#2048: Implements the same collab D1 sponsored delegation path across storage, delegation handling, peer routing, and tests.
  • jaylfc/taOS#1667: Updates decision routing for delegation-gate approval and denial responses.
  • jaylfc/taOS#2025: Introduces the peer inbox dispatch infrastructure extended here for delegation requests.

Suggested reviewers: hognek

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title mentions Delegation A3 and minted invite delivery, which matches the main change in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch exec/tsk-2wbp3f

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

PR Summary by Qodo

Add sponsored agent delegation and revocation workflow

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Adds policy-gated peer delegation with manual or automatic project invite approval.
• Persists sponsor provenance through invite redemption and agent registration.
• Supports sponsor revocation, task cleanup, peer panic controls, and delegation tests.
Diagram

sequenceDiagram
    actor Contact as Remote Contact
    participant Peer as Peer Inbox
    participant Handler as Delegation Handler
    participant Project as Project Store
    participant Gate as Decisions Gate
    participant Invites as Invite Store
    participant Redeem as Invite Redeemer
    participant Registry as Agent Registry
    Contact->>Peer: delegation request
    Peer->>Handler: process request
    Handler->>Project: verify member and policy
    Project-->>Handler: membership and policy
    alt Manual approval
        Handler->>Gate: create blocking decision
        Gate->>Handler: approved metadata
    else Auto approval
        Note over Handler: Policy permits automatic approval
    end
    Handler->>Invites: mint sponsored invite
    Invites-->>Handler: invite identifier
    Handler-->>Peer: approval result
    Peer-->>Contact: deliver invite result
    Contact->>Redeem: redeem invite
    Redeem->>Registry: register sponsor link
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Persisted delegation workflow
  • ➕ Provides explicit pending, approved, denied, delivered, and revoked states
  • ➕ Improves idempotency, auditing, retries, and duplicate-request handling
  • ➕ Decouples delegation lifecycle state from Decisions metadata
  • ➖ Requires another schema, store, and lifecycle API
  • ➖ Duplicates some state already represented by Decisions and project invites
  • ➖ Adds migration and cleanup complexity for an initial protocol version
2. Transactional invite outbox
  • ➕ Makes remote invite delivery retryable and observable
  • ➕ Prevents successful minting from being separated from failed peer delivery
  • ➖ Requires an outbox worker and delivery acknowledgements
  • ➖ Adds operational complexity beyond the existing peer response mechanism

Recommendation: The PR's centralized handler and reuse of existing Decisions, invite, and registry stores is pragmatic for the initial workflow and avoids duplicating authorization logic. Keep this approach for v1, but introduce persisted delegation state or an outbox if delivery retries, cancellation, replay protection, or stronger lifecycle auditing become protocol requirements.

Files changed (9) +1124 / -48

Enhancement (8) +824 / -48
agent_registry_store.pyPersist and query sponsored agent identities +89/-15

Persist and query sponsored agent identities

• Adds an idempotently migrated sponsor_contact_id column and accepts sponsorship during registration. Provides APIs to list agents by sponsor and update or clear sponsor links.

tinyagentos/agent_registry_store.py

delegation_handler.pyImplement the sponsored delegation lifecycle +537/-0

Implement the sponsored delegation lifecycle

• Introduces delegation envelope validation, scope policy enforcement, membership checks, manual and automatic approvals, and sponsored invite minting. Also adds cascade revocation, task unassignment, contact-level suspension, and an instance-wide peer panic switch.

tinyagentos/delegation_handler.py

invite_store.pyStore structured metadata on project invites +39/-22

Store structured metadata on project invites

• Adds an idempotently migrated JSON metadata column and accepts metadata during invite minting. Deserializes scopes and metadata when reading invite records.

tinyagentos/projects/invite_store.py

project_store.pyAdd membership and delegation policy helpers +58/-0

Add membership and delegation policy helpers

• Adds member-kind-aware project membership checks. Provides generic getters and setters for individual project settings, including delegation auto-approval policy.

tinyagentos/projects/project_store.py

agent_auth_requests.pyPropagate sponsorship during agent approval +10/-0

Propagate sponsorship during agent approval

• Extends agent approval to attach sponsor_contact_id when registering a new delegated identity or reusing an existing identity for another project.

tinyagentos/routes/agent_auth_requests.py

decisions.pyComplete approved collaboration delegations +54/-8

Complete approved collaboration delegations

• Recognizes collaboration delegation decisions and mints the sponsored invite after approval. Routes approval, denial, and completion-failure results without invalidating an already persisted decision answer.

tinyagentos/routes/decisions.py

peer.pyDispatch delegation envelopes and enforce peer panic mode +17/-3

Dispatch delegation envelopes and enforce peer panic mode

• Routes delegation_request peer envelopes into the delegation handler. Rejects all peer authentication with HTTP 503 while the instance-level panic switch is active.

tinyagentos/routes/peer.py

project_invites.pyCarry invite sponsorship into agent registration +20/-0

Carry invite sponsorship into agent registration

• Extracts sponsor_contact_id from delegation invite metadata during project-level and OS-level redemption. Passes the sponsor through the shared agent approval workflow.

tinyagentos/routes/project_invites.py

Tests (1) +300 / -0
test_collab_d1_delegation.pyCover delegation validation and sponsor persistence +300/-0

Cover delegation validation and sponsor persistence

• Adds tests for scope denylisting, envelope validation, sponsor registration queries and updates, the registry migration, and invite metadata deserialization.

tests/test_collab_d1_delegation.py

@jaylfc

jaylfc commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-kilo review

VERDICT: Multiple blocking issues — incomplete function, missing method, potential migration ordering, and insufficient test coverage.

  • tinyagentos/delegation_handler.py:502_apply_collab_delegation_grant is cut off mid-import (from tinyagentos.delegation_handler ). This will cause a syntax error and the collab delegation approval path is non-functional.

  • tinyagentos/agent_registry_store.pycascade_sponsor_revoke calls registry.set_status(canonical_id, "revoked", ...) but no set_status method exists in the diff. The store has set_sponsor but not set_status.

  • tinyagentos/delegation_handler.py:388_unassign_agent_tasks assumes task_store.list_for_assignee() or task_store.list_tasks(project_id=..., status="in_progress") exist. Neither is verified in the diff; if the task store lacks these, cascade revocation silently fails.

  • tinyagentos/agent_registry_store.py:438 — Migration v5 runs after v4 in _post_init, but v5 checks PRAGMA table_info on a connection that may not have the v4 changes committed yet (v4 does await conn.commit() but v5 uses self._db which is the same connection). Order-dependent but likely works; still fragile.

  • tinyagentos/projects/invite_store.py:470_row_to_dict now deserializes scopes from JSON string to list. Existing code calling mint() expects scopes to be a list in the returned dict (line 257 returns "scopes": scopes directly). This breaks callers that rely on scopes already being a list.

  • tests/test_collab_d1_delegation.py — No integration tests for the actual delegation flow: process_delegation_request, complete_delegation_approval, cascade_sponsor_revoke, kill-switches, or auto-approve path. Only unit tests for validation helpers and registry CRUD.

  • tinyagentos/delegation_handler.py:167process_delegation_request accesses request.app.state.project_store but nothing in the diff shows this being attached to app.state. Same for decision_store, project_invite_store, agent_registry, contacts_store, chat_channels, chat_messages, config, project_task_store. If wiring is missing, all delegation requests return "store not available".

  • tinyagentos/routes/agent_auth_requests.py:457approve_request_record calls registry.set_sponsor(existing_cid, sponsor_contact_id) when reusing an identity, but set_sponsor returns the updated record which is ignored. Not a bug but inconsistent with the new pattern.
    VERDICT: Multiple blocking issues — incomplete function, missing method, potential migration ordering, and insufficient test coverage.

  • tinyagentos/delegation_handler.py:502_apply_collab_delegation_grant is cut off mid-import (from tinyagentos.delegation_handler ). This will cause a syntax error and the collab delegation approval path is non-functional.

  • tinyagentos/agent_registry_store.pycascade_sponsor_revoke calls registry.set_status(canonical_id, "revoked", ...) but no set_status method exists in the diff. The store has set_sponsor but not set_status.

  • tinyagentos/delegation_handler.py:388_unassign_agent_tasks assumes task_store.list_for_assignee() or task_store.list_tasks(project_id=..., status="in_progress") exist. Neither is verified in the diff; if the task store lacks these, cascade revocation silently fails.

  • tinyagentos/agent_registry_store.py:438 — Migration v5 runs after v4 in _post_init, but v5 checks PRAGMA table_info on a connection that may not have the v4 changes committed yet (v4 does await conn.commit() but v5 uses self._db which is the same connection). Order-dependent but likely works; still fragile.

  • tinyagentos/projects/invite_store.py:470_row_to_dict now deserializes scopes from JSON string to list. Existing code calling mint() expects scopes to be a list in the returned dict (line 257 returns "scopes": scopes directly). This breaks callers that rely on scopes already being a list.

  • tests/test_collab_d1_delegation.py — No integration tests for the actual delegation flow: process_delegation_request, complete_delegation_approval, cascade_sponsor_revoke, kill-switches, or auto-approve path. Only unit tests for validation helpers and registry CRUD.

  • tinyagentos/delegation_handler.py:167process_delegation_request accesses request.app.state.project_store but nothing in the diff shows this being attached to app.state. Same for decision_store, project_invite_store, agent_registry, contacts_store, chat_channels, chat_messages, config, project_task_store. If wiring is missing, all delegation requests return "store not available".

  • tinyagentos/routes/agent_auth_requests.py:457approve_request_record calls registry.set_sponsor(existing_cid, sponsor_contact_id) when reusing an identity, but set_sponsor returns the updated record which is ignored. Not a bug but inconsistent with the new pattern.

Automated first-pass review by the nemotron-ultra-kilo lane. The lead still reviews before merge.

@jaylfc

jaylfc commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-orB review

VERDICT: Several correctness bugs, one security issue, and missing test coverage

  • delegation_handler.py:155_validate_delegation_envelope_body validates requested_scopes against SPONSORED_DEFAULT_SCOPES | SPONSORED_DENY_SCOPES but unknown scopes are only logged, not denied. validate_delegation_scopes (line 79) then grants all non-deny-listed scopes, allowing arbitrary scope escalation (e.g., admin_delete passes validation and gets granted).
  • delegation_handler.py:337_auto_approve_delegation passes project_id to invite_store.mint but mint's signature in the test expects project_id as first arg; however _auto_approve_delegation passes scopes=non_project_scopes + project_scopes where project_scopes includes project_tasks/canvas_read — the invite store's JWT minting expects project-scoped scopes to bind to project_id, but non_project_scopes (e.g., a2a_send, a2a_receive, registry_feeds_read) are also passed without project binding. This may over-grant.
  • delegation_handler.py:390complete_delegation_approval passes scopes=granted_scopes directly without splitting project vs non-project scopes, same over-grant risk.
  • delegation_handler.py:438cascade_sponsor_revoke calls registry.set_status(canonical_id, "revoked", actor=contact_id) but AgentRegistryStore.set_status signature (not shown in diff) likely expects actor as a keyword arg; need to verify signature matches.
  • delegation_handler.py:438cascade_sponsor_revoke parameter project_id: str | None = None is unused in the list_by_sponsor call (line 440). Cascade by project is a no-op — all sponsored agents are revoked regardless of project.
  • agent_registry_store.py:238_migration_v5_add_sponsor_contact_id uses await (await conn.execute(...)).fetchall() which is wrong; conn.execute returns a cursor, not an awaitable. Should be cursor = await conn.execute(...); rows = await cursor.fetchall().
  • agent_registry_store.py:507register INSERT includes sponsor_contact_id but the column is nullable with no default; the INSERT statement has 13 placeholders but only 12 values before sponsor_contact_id (count: canonical_id, display_name, framework, user_id, origin, handle, role, title, reports_to, caps_json, created_ts, initial_status, sponsor_contact_id = 13 — correct count, but verify column order matches schema).
  • agent_registry_store.py:666set_sponsor calls await self.get(canonical_id) twice (once before UPDATE, once after). Could return the updated row directly via RETURNING clause (SQLite 3.35+).
  • agent_registry_store.py:238 — Migration _migration_v5_add_sponsor_contact_id is not idempotent across restarts if the column was added manually but the migration version tracking wasn't updated; relies only on column existence check.
  • test_collab_d1_delegation.py — No tests for: process_delegation_request (happy path, auto-approve, manual-approve, contact-not-member, missing project store, missing decision store), complete_delegation_approval, cascade_sponsor_revoke, validate_delegation_scopes with unknown/elevated scopes, scope escalation via unknown scopes.
  • Securityvalidate_delegation_scopes grants any scope not in SPONSORED_DENY_SCOPES (line 79), allowing scope escalation via unknown scopes (e.g., admin_delete, secrets_read). Design doc says "require explicit per-scope Decisions approval" but code auto-grants.
  • Styledelegation_handler.py:337 _auto_approve_delegation and complete_delegation_approval duplicate invite-minting logic (DRY violation). agent_registry_store.py:238 migration uses PRAGMA table_info with set comprehension over row[1] — fragile if PRAGMA output format changes.

Automated first-pass review by the nemotron-ultra-orB lane. The lead still reviews before merge.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (7) 📘 Rule violations (0) 📜 Skill insights (3)

Context used
✅ Compliance rules (platform): 35 rules

Grey Divider


Action required

1. sponsor_contact_id appears in SCHEMA 📜 Skill insight ≡ Correctness
Description
The migration-added sponsor_contact_id column is also declared in the initial schema. This
violates the required separation between first-open DDL and columns retrofitted through guarded
migration logic.
Code

tinyagentos/agent_registry_store.py[49]

+    sponsor_contact_id  TEXT
Relevance

⭐⭐⭐ High

Explicit compliance rule and deterministic migration-schema duplication make this a straightforward
fix.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2185190 prohibits migration-added columns from appearing in SCHEMA. The branch
declares sponsor_contact_id in SCHEMA and separately adds the same column through
_migration_v5_add_sponsor_contact_id.

tinyagentos/agent_registry_store.py[49-49]
tinyagentos/agent_registry_store.py[209-226]
Skill: taos-development-skill

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The migration-added `sponsor_contact_id` column is declared in `SCHEMA`, contrary to the store migration requirements.

## Issue Context
`_migration_v5_add_sponsor_contact_id()` already performs a guarded `PRAGMA table_info` check and conditionally adds the column during `_post_init`.

## Fix Focus Areas
- tinyagentos/agent_registry_store.py[49-49]
- tinyagentos/agent_registry_store.py[209-226]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. metadata appears in SCHEMA 📜 Skill insight ≡ Correctness
Description
The migration-added metadata column is also declared in the initial project-invite schema. The
checklist requires this column to be added only through the guarded _post_init retrofit path.
Code

tinyagentos/projects/invite_store.py[36]

+    metadata             TEXT NOT NULL DEFAULT '{}'
Relevance

⭐⭐⭐ High

Exact compliance violation duplicates a guarded migration column in initial DDL.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The initial schema declares metadata, while _post_init checks for the column and executes `ALTER
TABLE ... ADD COLUMN metadata`. This is the exact schema/migration duplication forbidden by PR
Compliance ID 2185190.

tinyagentos/projects/invite_store.py[36-36]
tinyagentos/projects/invite_store.py[114-118]
Skill: taos-development-skill

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `metadata` column is both present in `SCHEMA` and conditionally added by `_post_init`.

## Issue Context
PR Compliance ID 2185190 requires migration-added columns to remain outside first-open schema DDL and be retrofitted with guarded initialization logic.

## Fix Focus Areas
- tinyagentos/projects/invite_store.py[36-36]
- tinyagentos/projects/invite_store.py[114-118]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Approved delegation undefined reply 🐞 Bug ≡ Correctness
Description
_apply_delegation_grant no longer assigns reply for either approved outcome but still uses it
unconditionally. Every approved existing delegation raises UnboundLocalError after the assignment
attempt, potentially returning 500 after the task was already changed.
Code

tinyagentos/routes/decisions.py[L419-424]

-    if not approved:
-        reply = "delegation denied"
-    elif completed:
-        reply = "delegation approved - the task has been assigned"
    else:
-        reply = "delegation approved, but assigning the task failed - please retry"
+        reply = f"The delegation to {to_agent} was denied."
Relevance

⭐⭐⭐ High

Approved branches leave reply unbound, an obvious deterministic runtime failure.

PR-#485

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The current function initializes reply only in the denial branch, while the existing tests require
successful and failed approved paths to return 200 and route an outcome-specific message.

tinyagentos/routes/decisions.py[370-421]
tests/test_routes_delegation.py[283-356]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Restore reply assignment for every approved delegation outcome before `_route_answer_to_agent` is called.

## Issue Context
Successful completion should report assignment success, while failed completion should report that a retry is needed. Existing tests assert both behaviors.

## Fix Focus Areas
- tinyagentos/routes/decisions.py[370-421]
- tests/test_routes_delegation.py[283-356]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (5)
4. Wrong invite-store state key 🐞 Bug ≡ Correctness
Description
Both delegation approval paths look up request.app.state.project_invite_store, but startup
publishes the initialized store as app.state.project_invites. In the production app wiring, every
approval therefore returns invite store not available before minting an invite.
Code

tinyagentos/delegation_handler.py[R264-266]

+    invite_store = getattr(request.app.state, "project_invite_store", None)
+    if invite_store is None:
+        return {"status": "error", "error": "invite store not available"}
Relevance

⭐⭐⭐ High

Deterministic state-key mismatch makes both production approval paths unreachable.

PR-#274
PR-#280

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Application startup assigns only app.state.project_invites, and existing invite routes
consistently read that attribute, proving the new lookup returns None.

tinyagentos/delegation_handler.py[264-266]
tinyagentos/delegation_handler.py[324-326]
tinyagentos/app.py[1597-1597]
tinyagentos/routes/project_invites.py[1092-1094]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Use the application-state attribute under which `ProjectInviteStore` is actually registered.

## Issue Context
Startup and all existing invite routes use `app.state.project_invites`; the new handler uses a nonexistent `project_invite_store` attribute in both approval paths.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[264-266]
- tinyagentos/delegation_handler.py[324-326]
- tinyagentos/app.py[1597-1597]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Mint result uses wrong key 🐞 Bug ≡ Correctness
Description
Both approval paths access invite["id"], while ProjectInviteStore.mint returns the identifier as
invite["record"]["invite_id"]. Once the store lookup is fixed, every approval will commit a
pending invite and then fail while constructing its result.
Code

tinyagentos/delegation_handler.py[R294-297]

+    return {
+        "status": "approved",
+        "invite_id": invite["id"],
+        "agent_slug": agent_slug,
Relevance

⭐⭐⭐ High

Return contract visibly exposes record.invite_id, making the top-level id lookup an obvious bug.

PR-#248
PR-#280

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The store's return object has only record and pin at the top level, whereas both new handlers
read a nonexistent top-level id after minting has committed.

tinyagentos/delegation_handler.py[294-298]
tinyagentos/delegation_handler.py[350-354]
tinyagentos/projects/invite_store.py[227-263]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Read the minted invite identifier from the actual `ProjectInviteStore.mint` return structure in both approval paths.

## Issue Context
`mint` returns `{record: {..., invite_id: ...}, pin: ...}` and commits before returning, so the current `invite["id"]` access fails after leaving an orphaned pending invite.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[294-298]
- tinyagentos/delegation_handler.py[350-354]
- tinyagentos/projects/invite_store.py[241-263]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Approval never reaches peer 🐞 Bug ≡ Correctness
Description
Manual approval and denial results are passed to the local @-agent answer router even though the
decision's from_agent is a contact ID such as hub:hogne. That router immediately ignores non-@
identifiers, and no peer polling or outbound result route was added, so the sponsor never receives
the completed decision or invite.
Code

tinyagentos/routes/decisions.py[463]

+            await _route_answer_to_agent(decision, reply)
Relevance

⭐⭐⭐ High

Missing peer delivery directly contradicts this PR's stated invite-delivery intent.

PR-#1662

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The decision is created with the raw contact ID, while _route_answer_to_agent returns immediately
unless from_agent starts with @. The peer route only accepts incoming delegation requests and
exposes no result polling or delivery path.

tinyagentos/delegation_handler.py[219-246]
tinyagentos/routes/decisions.py[45-64]
tinyagentos/routes/decisions.py[441-466]
tinyagentos/routes/peer.py[259-273]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Deliver delegation approval and denial results to the sponsoring contact through the authenticated peer channel rather than the local agent A2A router.

## Issue Context
Delegation decisions store `from_agent=contact_id`; `_route_answer_to_agent` only accepts identifiers beginning with `@`. Persist an outbound result or send a signed peer envelope containing the outcome and invite details.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[219-246]
- tinyagentos/routes/decisions.py[424-472]
- tinyagentos/routes/peer.py[259-273]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Denied request gains scope 🐞 Bug ⛨ Security
Description
After hard-denied scopes are stripped, the handler does not reject an empty grant set and continues
to mint a project invite. Because ProjectInviteStore.mint automatically adds project_tasks, a
request containing only files_write can produce an invite granting an unrequested project scope.
Code

tinyagentos/delegation_handler.py[R153-154]

+    # Apply scope denylist.
+    granted_scopes, denied_scopes = validate_delegation_scopes(requested_scopes)
Relevance

⭐⭐⭐ High

Concrete privilege expansion contradicts the denylist; team historically accepts authorization
guards.

PR-#248
PR-#304

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The handler immediately proceeds after filtering and has no empty-result guard. The invite store
then force-adds project_tasks to every project-scoped invite, proving the denied-only request
receives a surviving capability it never requested.

tinyagentos/delegation_handler.py[153-181]
tinyagentos/projects/invite_store.py[181-186]
tests/test_collab_d1_delegation.py[15-50]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Reject delegation requests when no grantable scopes remain after applying the hard denylist.

## Issue Context
The envelope's original scope list can be nonempty but become empty after filtering. Passing that empty list into a project invite silently adds `project_tasks`, widening authorization beyond the request.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[153-181]
- tinyagentos/projects/invite_store.py[181-186]
- tests/test_collab_d1_delegation.py[15-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Project revoke disables identity 🐞 Bug ≡ Correctness
Description
cascade_sponsor_revoke(project_id=...) promises project-only revocation but globally changes every
sponsored identity to revoked without filtering by project. Revoking collaboration on one project
therefore disables those identities and their access to every other project.
Code

tinyagentos/delegation_handler.py[384]

+            await registry.set_status(canonical_id, "revoked", actor=contact_id)
Relevance

⭐⭐ Medium

Impact is serious, but project-only versus identity-wide revocation semantics are architectural and
historically mixed.

PR-#1662

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new function documents project-scoped behavior but always lists every active identity for the
sponsor and applies a global registry lifecycle transition. The registry implementation confirms
that this writes global status and revoked_at fields.

tinyagentos/delegation_handler.py[357-385]
tinyagentos/agent_registry_store.py[696-743]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Keep project membership revocation scoped to the supplied project and reserve global registry revocation for contact-wide revocations.

## Issue Context
`AgentRegistryStore.set_status(..., "revoked")` changes the identity's global lifecycle state and `revoked_at`; it cannot implement the documented project-token-only behavior.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[357-390]
- tinyagentos/agent_registry_store.py[696-743]
- tinyagentos/projects/project_store.py[319-331]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

9. Added text contains em dashes 📜 Skill insight ✧ Quality
Description
The PR introduces em dashes in docstrings, comments, and runtime text across several changed files.
The checklist prohibits U+2014 in all such public-facing text.
Code

tinyagentos/delegation_handler.py[1]

+"""Agent delegation handler — cross-user collab D1.
Relevance

⭐⭐⭐ High

Mechanical checklist violation across newly added text; trivial replacement is likely accepted.

PR-#372

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2212258 forbids em dashes in comments, docstrings, user-facing strings, and other
public-facing text. Added occurrences include the delegation module docstring, a user-facing
hard-denial message, a runtime warning, route documentation, and test text.

tinyagentos/delegation_handler.py[1-1]
tinyagentos/delegation_handler.py[213-214]
tinyagentos/delegation_handler.py[531-531]
tinyagentos/agent_registry_store.py[480-482]
tinyagentos/routes/agent_auth_requests.py[371-373]
tests/test_collab_d1_delegation.py[1-1]
Skill: taos-development-skill

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Added docstrings, comments, and strings contain prohibited em dash characters (`—`).

## Issue Context
Replace each U+2014 character with a regular hyphen, an en dash, or rewritten punctuation without changing behavior.

## Fix Focus Areas
- tests/test_collab_d1_delegation.py[1-162]
- tinyagentos/agent_registry_store.py[480-482]
- tinyagentos/delegation_handler.py[1-531]
- tinyagentos/routes/agent_auth_requests.py[371-373]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Revocation cannot unassign tasks 🐞 Bug ≡ Correctness
Description
The revocation helper calls update_task(..., assignee_id=None, status="ready"), but
ProjectTaskStore.update_task treats None as “unchanged,” and the board represents ready work
with status open. Affected tasks retain the revoked assignee and are moved outside the ready-task
view.
Code

tinyagentos/delegation_handler.py[478]

+            await task_store.update_task(task_id, assignee_id=None, status="ready")
Relevance

⭐⭐⭐ High

Close task-store precedent uses NULL assignment and open status when releasing tasks.

PR-#260

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The helper passes None and ready, while the task store skips all None candidate fields and
defines ready tasks as unclaimed rows with status open.

tinyagentos/delegation_handler.py[447-483]
tinyagentos/projects/task_store.py[70-81]
tinyagentos/projects/task_store.py[246-295]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Provide an explicit supported way to clear a task assignee and return revoked-agent tasks to the board's `open` state.

## Issue Context
The current task update API ignores `None` values, and `ready_tasks` selects unclaimed tasks whose status is `open`, not `ready`. The assignee lookup should also support optional project filtering directly.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[447-483]
- tinyagentos/projects/task_store.py[70-81]
- tinyagentos/projects/task_store.py[246-295]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

created_ts TEXT NOT NULL,
revoked_at TEXT,
status TEXT NOT NULL DEFAULT 'active',
sponsor_contact_id TEXT

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. sponsor_contact_id appears in schema 📜 Skill insight ≡ Correctness

The migration-added sponsor_contact_id column is also declared in the initial schema. This
violates the required separation between first-open DDL and columns retrofitted through guarded
migration logic.
Agent Prompt
## Issue description
The migration-added `sponsor_contact_id` column is declared in `SCHEMA`, contrary to the store migration requirements.

## Issue Context
`_migration_v5_add_sponsor_contact_id()` already performs a guarded `PRAGMA table_info` check and conditionally adds the column during `_post_init`.

## Fix Focus Areas
- tinyagentos/agent_registry_store.py[49-49]
- tinyagentos/agent_registry_store.py[209-226]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

pin_required INTEGER NOT NULL DEFAULT 1,
contact_id TEXT
contact_id TEXT,
metadata TEXT NOT NULL DEFAULT '{}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. metadata appears in schema 📜 Skill insight ≡ Correctness

The migration-added metadata column is also declared in the initial project-invite schema. The
checklist requires this column to be added only through the guarded _post_init retrofit path.
Agent Prompt
## Issue description
The `metadata` column is both present in `SCHEMA` and conditionally added by `_post_init`.

## Issue Context
PR Compliance ID 2185190 requires migration-added columns to remain outside first-open schema DDL and be retrofitted with guarded initialization logic.

## Fix Focus Areas
- tinyagentos/projects/invite_store.py[36-36]
- tinyagentos/projects/invite_store.py[114-118]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@@ -0,0 +1,537 @@
"""Agent delegation handler — cross-user collab D1.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Added text contains em dashes 📜 Skill insight ✧ Quality

The PR introduces em dashes in docstrings, comments, and runtime text across several changed files.
The checklist prohibits U+2014 in all such public-facing text.
Agent Prompt
## Issue description
Added docstrings, comments, and strings contain prohibited em dash characters (`—`).

## Issue Context
Replace each U+2014 character with a regular hyphen, an en dash, or rewritten punctuation without changing behavior.

## Fix Focus Areas
- tests/test_collab_d1_delegation.py[1-162]
- tinyagentos/agent_registry_store.py[480-482]
- tinyagentos/delegation_handler.py[1-531]
- tinyagentos/routes/agent_auth_requests.py[371-373]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines -419 to -424
if not approved:
reply = "delegation denied"
elif completed:
reply = "delegation approved - the task has been assigned"
else:
reply = "delegation approved, but assigning the task failed - please retry"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Approved delegation undefined reply 🐞 Bug ≡ Correctness

_apply_delegation_grant no longer assigns reply for either approved outcome but still uses it
unconditionally. Every approved existing delegation raises UnboundLocalError after the assignment
attempt, potentially returning 500 after the task was already changed.
Agent Prompt
## Issue description
Restore reply assignment for every approved delegation outcome before `_route_answer_to_agent` is called.

## Issue Context
Successful completion should report assignment success, while failed completion should report that a retry is needed. Existing tests assert both behaviors.

## Fix Focus Areas
- tinyagentos/routes/decisions.py[370-421]
- tests/test_routes_delegation.py[283-356]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +264 to +266
invite_store = getattr(request.app.state, "project_invite_store", None)
if invite_store is None:
return {"status": "error", "error": "invite store not available"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

5. Wrong invite-store state key 🐞 Bug ≡ Correctness

Both delegation approval paths look up request.app.state.project_invite_store, but startup
publishes the initialized store as app.state.project_invites. In the production app wiring, every
approval therefore returns invite store not available before minting an invite.
Agent Prompt
## Issue description
Use the application-state attribute under which `ProjectInviteStore` is actually registered.

## Issue Context
Startup and all existing invite routes use `app.state.project_invites`; the new handler uses a nonexistent `project_invite_store` attribute in both approval paths.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[264-266]
- tinyagentos/delegation_handler.py[324-326]
- tinyagentos/app.py[1597-1597]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +294 to +297
return {
"status": "approved",
"invite_id": invite["id"],
"agent_slug": agent_slug,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

6. Mint result uses wrong key 🐞 Bug ≡ Correctness

Both approval paths access invite["id"], while ProjectInviteStore.mint returns the identifier as
invite["record"]["invite_id"]. Once the store lookup is fixed, every approval will commit a
pending invite and then fail while constructing its result.
Agent Prompt
## Issue description
Read the minted invite identifier from the actual `ProjectInviteStore.mint` return structure in both approval paths.

## Issue Context
`mint` returns `{record: {..., invite_id: ...}, pin: ...}` and commits before returning, so the current `invite["id"]` access fails after leaving an orphaned pending invite.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[294-298]
- tinyagentos/delegation_handler.py[350-354]
- tinyagentos/projects/invite_store.py[241-263]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

f"Invite ID: {invite_id}. "
f"Share this invite with the remote contact so their agent can join."
)
await _route_answer_to_agent(decision, reply)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

7. Approval never reaches peer 🐞 Bug ≡ Correctness

Manual approval and denial results are passed to the local @-agent answer router even though the
decision's from_agent is a contact ID such as hub:hogne. That router immediately ignores non-@
identifiers, and no peer polling or outbound result route was added, so the sponsor never receives
the completed decision or invite.
Agent Prompt
## Issue description
Deliver delegation approval and denial results to the sponsoring contact through the authenticated peer channel rather than the local agent A2A router.

## Issue Context
Delegation decisions store `from_agent=contact_id`; `_route_answer_to_agent` only accepts identifiers beginning with `@`. Persist an outbound result or send a signed peer envelope containing the outcome and invite details.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[219-246]
- tinyagentos/routes/decisions.py[424-472]
- tinyagentos/routes/peer.py[259-273]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

for agent in sponsored:
canonical_id = agent["canonical_id"]
try:
await registry.set_status(canonical_id, "revoked", actor=contact_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

8. Project revoke disables identity 🐞 Bug ≡ Correctness

cascade_sponsor_revoke(project_id=...) promises project-only revocation but globally changes every
sponsored identity to revoked without filtering by project. Revoking collaboration on one project
therefore disables those identities and their access to every other project.
Agent Prompt
## Issue description
Keep project membership revocation scoped to the supplied project and reserve global registry revocation for contact-wide revocations.

## Issue Context
`AgentRegistryStore.set_status(..., "revoked")` changes the identity's global lifecycle state and `revoked_at`; it cannot implement the documented project-token-only behavior.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[357-390]
- tinyagentos/agent_registry_store.py[696-743]
- tinyagentos/projects/project_store.py[319-331]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

if not task_id:
continue
try:
await task_store.update_task(task_id, assignee_id=None, status="ready")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

9. Revocation cannot unassign tasks 🐞 Bug ≡ Correctness

The revocation helper calls update_task(..., assignee_id=None, status="ready"), but
ProjectTaskStore.update_task treats None as “unchanged,” and the board represents ready work
with status open. Affected tasks retain the revoked assignee and are moved outside the ready-task
view.
Agent Prompt
## Issue description
Provide an explicit supported way to clear a task assignee and return revoked-agent tasks to the board's `open` state.

## Issue Context
The current task update API ignores `None` values, and `ready_tasks` selects unclaimed tasks whose status is `open`, not `ready`. The assignee lookup should also support optional project filtering directly.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[447-483]
- tinyagentos/projects/task_store.py[70-81]
- tinyagentos/projects/task_store.py[246-295]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +153 to +154
# Apply scope denylist.
granted_scopes, denied_scopes = validate_delegation_scopes(requested_scopes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

10. Denied request gains scope 🐞 Bug ⛨ Security

After hard-denied scopes are stripped, the handler does not reject an empty grant set and continues
to mint a project invite. Because ProjectInviteStore.mint automatically adds project_tasks, a
request containing only files_write can produce an invite granting an unrequested project scope.
Agent Prompt
## Issue description
Reject delegation requests when no grantable scopes remain after applying the hard denylist.

## Issue Context
The envelope's original scope list can be nonempty but become empty after filtering. Passing that empty list into a project invite silently adds `project_tasks`, widening authorization beyond the request.

## Fix Focus Areas
- tinyagentos/delegation_handler.py[153-181]
- tinyagentos/projects/invite_store.py[181-186]
- tests/test_collab_d1_delegation.py[15-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tinyagentos/routes/decisions.py (1)

370-421: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Critical: reply is unbound when approved is True — breaks every approved delegation_gate decision.

reply is only assigned in the else (denial) branch (line 419). When value == "approve", the if approved: block (lines 389-417) never sets reply, so the unconditional await _route_answer_to_agent(decision, reply) at line 420 raises UnboundLocalError. This is a regression on the pre-existing #161 gated-delegation approve flow — every approval of a delegation_gate decision will now crash instead of notifying the delegate.

🐛 Proposed fix
             if completed:
                 try:
                     await policies.add_grant(from_agent, "delegate", decision.get("id"))
                 except Exception:
                     logger.warning(
                         "delegate grant write failed for agent %s (decision %s)",
                         from_agent, decision.get("id"), exc_info=True,
                     )
+        reply = (
+            f"The delegation to {to_agent} was approved and completed."
+            if completed
+            else f"The delegation to {to_agent} was approved, but completion failed."
+        )
     else:
         reply = f"The delegation to {to_agent} was denied."
     await _route_answer_to_agent(decision, reply)
     return True
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/routes/decisions.py` around lines 370 - 421, Initialize or assign
reply within the approved branch of _apply_delegation_grant before the
unconditional _route_answer_to_agent call, while preserving the existing denial
message. Ensure every approved delegation_gate decision produces the intended
success notification without raising UnboundLocalError.
🧹 Nitpick comments (2)
tests/test_collab_d1_delegation.py (2)

85-119: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused parsed bindings flagged by Ruff (RUF059).

Lines 88, 100, and 112 destructure parsed from _validate_delegation_envelope_body(...) but never use it. Prefix with _ to silence the lint (e.g. ok, err, _parsed = ...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_collab_d1_delegation.py` around lines 85 - 119, Update the three
test methods test_empty_scopes, test_scopes_not_a_list, and
test_empty_agent_slug to bind the unused third return value from
_validate_delegation_envelope_body to a name prefixed with an underscore, such
as _parsed, while preserving the existing assertions.

Source: Linters/SAST tools


126-301: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No end-to-end coverage for the mint/approve paths.

This module covers scope/envelope validation and store-level sponsor/metadata plumbing well, but there's no test exercising _auto_approve_delegation, complete_delegation_approval, or process_delegation_request end-to-end against a real ProjectInviteStore. That gap is why the invite["id"] KeyError in tinyagentos/delegation_handler.py (flagged separately) went undetected — a test asserting the returned invite_id from either function would have caught it immediately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_collab_d1_delegation.py` around lines 126 - 301, Add end-to-end
async tests covering _auto_approve_delegation, complete_delegation_approval, and
process_delegation_request with a real ProjectInviteStore, asserting each path
returns the persisted invite_id and completes the expected approval flow. Use
the existing delegation test fixtures and metadata/scope setup where applicable,
including the sponsored delegation path that would expose incorrect invite["id"]
access.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tinyagentos/delegation_handler.py`:
- Around line 249-298: The delegation approval flows use the wrong mint result
shape and configure pin requirements through metadata. Update
_auto_approve_delegation and complete_delegation_approval to read the invite ID
from mint()'s returned record and provide the expected invite_id
response/envelope key, pass pin_required=False directly to
ProjectInviteStore.mint(), and remove pin_required from the metadata payload so
the invite is stored without a PIN.

---

Outside diff comments:
In `@tinyagentos/routes/decisions.py`:
- Around line 370-421: Initialize or assign reply within the approved branch of
_apply_delegation_grant before the unconditional _route_answer_to_agent call,
while preserving the existing denial message. Ensure every approved
delegation_gate decision produces the intended success notification without
raising UnboundLocalError.

---

Nitpick comments:
In `@tests/test_collab_d1_delegation.py`:
- Around line 85-119: Update the three test methods test_empty_scopes,
test_scopes_not_a_list, and test_empty_agent_slug to bind the unused third
return value from _validate_delegation_envelope_body to a name prefixed with an
underscore, such as _parsed, while preserving the existing assertions.
- Around line 126-301: Add end-to-end async tests covering
_auto_approve_delegation, complete_delegation_approval, and
process_delegation_request with a real ProjectInviteStore, asserting each path
returns the persisted invite_id and completes the expected approval flow. Use
the existing delegation test fixtures and metadata/scope setup where applicable,
including the sponsored delegation path that would expose incorrect invite["id"]
access.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 00ef86e7-61e6-451e-b25b-a4d86ba668c0

📥 Commits

Reviewing files that changed from the base of the PR and between 5e6dcaa and 6c507c1.

📒 Files selected for processing (9)
  • tests/test_collab_d1_delegation.py
  • tinyagentos/agent_registry_store.py
  • tinyagentos/delegation_handler.py
  • tinyagentos/projects/invite_store.py
  • tinyagentos/projects/project_store.py
  • tinyagentos/routes/agent_auth_requests.py
  • tinyagentos/routes/decisions.py
  • tinyagentos/routes/peer.py
  • tinyagentos/routes/project_invites.py

Comment on lines +249 to +298
async def _auto_approve_delegation(
request,
*,
contact_id: str,
agent_slug: str,
display_name: str,
granted_scopes: list[str],
denied_scopes: list[str],
project_id: str,
) -> dict:
"""Auto-approve a delegation request (dev-swarm future path).

Mints a project invite (kind="agent", pin_required=false) and returns
the invite_id + connection_bundle.
"""
invite_store = getattr(request.app.state, "project_invite_store", None)
if invite_store is None:
return {"status": "error", "error": "invite store not available"}

# The project-scoped scopes require a non-null project_id for the JWT.
project_scopes = sorted(set(granted_scopes) & _PROJECT_SCOPES)
non_project_scopes = sorted(set(granted_scopes) - _PROJECT_SCOPES)

try:
invite = await invite_store.mint(
project_id=project_id,
scopes=non_project_scopes + project_scopes,
approval_mode="auto",
check_interval_secs=1800,
created_by=contact_id,
metadata={
"kind": "delegation_sponsored",
"sponsor_contact_id": contact_id,
"agent_slug": agent_slug,
"display_name": display_name,
"pin_required": False,
},
)
except Exception:
logger.warning(
"delegation: failed to create invite for %s / %s",
contact_id, agent_slug, exc_info=True,
)
return {"status": "error", "error": "failed to create project invite"}

return {
"status": "approved",
"invite_id": invite["id"],
"agent_slug": agent_slug,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching delegation / invite:\n'
git ls-files | rg '(delegation_handler|invite_store|decisions).py|test_collab_d1_delegation.py' || true

printf '\nOutline delegation_handler:\n'
if [ -f tinyagentos/delegation_handler.py ]; then
  ast-grep outline tinyagentos/delegation_handler.py || true
fi

printf '\nRelevant delegation_handler lines 220-370:\n'
sed -n '220,370p' tinyagentos/delegation_handler.py 2>/dev/null || true

printf '\nInvite store mint signature/return-ish:\n'
for f in $(git ls-files | rg 'tinyagentos/.*/invite_store.py$'); do
  echo "--- $f"
  wc -l "$f"
  rg -n "async def mint|def mint|return|invite_id|pin_required" "$f" -C 3 || true
done

printf '\nDecision route apply delegation snippet if present:\n'
for f in $(git ls-files | rg 'tinyagentos/routes/decisions.py$'); do
  echo "--- $f"
  rg -n "_apply_collab_delegation_grant|complete_delegation_approval" "$f" -C 8 || true
done

printf '\nTests for delegation approve auto/complete:\n'
if [ -f tests/test_collab_d1_delegation.py ]; then
  wc -l tests/test_collab_d1_delegation.py
  rg -n 'test_.*approve|auto|complete|invite_id|record|pin_required' tests/test_collab_d1_delegation.py -C 2 || true
fi

printf '\nSearch complete_delegation_approval callers/usages:\n'
rg -n "complete_delegation_approval|_auto_approve_delegation|invite\\[\"id\"\\]|invite\\['id'\\]" . || true

printf '\nSimple parser check for mint return shape declarations/usages:\n'
python3 - <<'PY'
from pathlib import Path
import re
for p in Path('.').glob('**/*.py'):
    if any(part.startswith('.') for part in p.parts):
        continue
    text=p.read_text(errors='ignore')
    if 'ProjectInviteStore' in text or 'async def mint' in text or 'def mint' in text:
        print(p)
PY

Repository: jaylfc/taOS

Length of output: 22982


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'process_delegation_request call path and exception handling:\n'
sed -n '107,184p' tinyagentos/delegation_handler.py 2>/dev/null || true

printf '\nDecision route _apply_collab_delegation_grant full:\n'
sed -n '424,470p' tinyagentos/routes/decisions.py 2>/dev/null || true

printf '\nProject invite routes handling connection_bundle / invite id:\n'
rg -n "connection_bundle|invite_id|pin_required|complete|auto|approve" tinyagentos/routes/project_invites.py -C 3 || true

printf '\nStatic check for invite["id"] and direct connection_bundle references in delegation handler:\n'
python3 - <<'PY'
from pathlib import Path
p=Path('tinyagentos/delegation_handler.py')
text=p.read_text()
for idx,line in enumerate(text.splitlines(),1):
    if 'invite["id"]' in line or "invite['id']" in line or 'connection_bundle' in line:
        print(f'{idx}: {line}')
PY

Repository: jaylfc/taOS

Length of output: 18880


Fix delegation invite response keys and pass pin_required into mint().

ProjectInviteStore.mint() returns {"record": {...}, "pin": ...}, so invite["id"] in both _auto_approve_delegation() and complete_delegation_approval() raises KeyError. Auto-approval will return the wrong peer-inbox envelope, while manual approval reaches the key not found: "invite_id" warning in _apply_collab_delegation_grant() instead of delivering the approved-delegation message. Also remove "pin_required" from metadata and pass pin_required=False to mint() so the invite row is created with pin_required = 0, matching these docstrings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/delegation_handler.py` around lines 249 - 298, The delegation
approval flows use the wrong mint result shape and configure pin requirements
through metadata. Update _auto_approve_delegation and
complete_delegation_approval to read the invite ID from mint()'s returned record
and provide the expected invite_id response/envelope key, pass
pin_required=False directly to ProjectInviteStore.mint(), and remove
pin_required from the metadata payload so the invite is stored without a PIN.

@jaylfc

jaylfc commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

Closing this rather than fix-forwarding it, for three reasons that compound.

  1. The approve path never worked. In routes/decisions.py the local 'reply' is assigned only in the else (denied) branch, then _route_answer_to_agent(decision, reply) runs unconditionally, so every APPROVAL raises UnboundLocalError. That is the four failing tests in TestDelegationApprovalFlow, and it is the primary path of the feature.

  2. Scope exploded well past the card. The card asked for delivery of the minted invite; this is 1124 insertions across 9 files, re-touching delegation_handler, invite_store, project_store, peer and agent_auth_requests.

  3. It collides on routes/decisions.py with fix(decisions): repair agent mirror path — middleware, identity, source, consent #2194 (agent mirror path, currently mid-fix on a security regression) and overlaps feat(collab): agent delegation handshake + sponsor_contact_id + cascades (D1) #2048's D1 territory. Landing it in this shape would force a three-way reconciliation of files that are actively moving.

No criticism of the lane: the card underspecified the ordering and let it grow. Recut as tsk- with the dependency enforced so it only dispatches after the decisions work settles, the scope pinned to delivery alone, and this exact UnboundLocalError named as a known trap. The branch stays on the remote if anyone wants the reference.

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