Skip to content

feat(engine): mailbox-access grants + admin API; admin IA fidelity doc (HT-54) - #72

Merged
zaridan merged 5 commits into
mainfrom
feat/ht-54-mailbox-access-engine
Jul 19, 2026
Merged

feat(engine): mailbox-access grants + admin API; admin IA fidelity doc (HT-54)#72
zaridan merged 5 commits into
mainfrom
feat/ht-54-mailbox-access-engine

Conversation

@zaridan

@zaridan zaridan commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #70, from the maintainer's fidelity review (HT-54). Pairs with the PR #71 web restructure (Permissions screen consumes these endpoints).

What's here

  • Spec draft.5 — mailbox-access semantics pinned (§3.4): admins implicit-all; auto-grant of all existing mailboxes to every newly created Agent (both provisioning paths + /setup, same transaction); conversation-visibility enforcement explicitly deferred to the multi-mailbox increment (conversations carry no mailbox_id — verified, documented, tracked on HT-55).
  • specs/ui/admin-ia.md — the admin IA & fidelity contract from the maintainer's review: three-scope rule (Manage / mailbox gear / avatar-personal-only), full surface index, module-extensibility as structure, the observed≠core inference ban, core-vs-module classification from the maintainer's purchased-module list, deliberate deviations, better-than deltas. Roadmap tickets HT-55…HT-65 filed against it.
  • StoreMailboxStore.listMailboxes() (unfiltered roster); AgentStore.listAgentMailboxIds / replaceAgentMailboxAccess (replace-set in one transaction, FK-translated invalid_mailbox, prior grants survive rollback); auto-grant via INSERT … SELECT inside the create transactions.
  • API — admin-only GET /api/v1/mailboxes (id/address/status only — no provider internals), GET/PUT /api/v1/agents/{id}/mailboxes; routed ahead of AGENT_ITEM so the suffix never mis-matches; acting-Agent enforcement identical to the other /agents/* routes.

Review & verification

Implemented (Sonnet) from the amended spec; coordinator review of the diff (store transaction semantics, FK translation, authz-first ordering, roster field exposure); gates run by the coordinator with real exit codes: typecheck ✅ lint ✅ 1055 tests / 50 files ✅. Browser-level verification happens with the PR #71 walkthrough against this branch's dev API.

No new dependencies; no mail-path changes.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added admin-only mailbox management APIs to list all mailboxes and view/replace an Agent’s mailbox grants (GET/PUT), requiring an acting Agent.
    • Agents are automatically granted access to all existing mailboxes on creation; grants can be replaced or cleared.
  • Documentation
    • Documented pinned per-Agent mailbox access semantics and updated Admin IA/fidelity scope rules.
  • Bug Fixes
    • Prevented orphan grant rows on duplicate-Agent creation and tightened mailbox grant validation/deduping.
  • Tests
    • Expanded routing, API, and store coverage; added mailbox store wiring and mailbox listing behavior in the test/dev harness.

zaridan and others added 3 commits July 18, 2026 17:02
… (HT-54)

- specs/ui/admin-ia.md: the three-scope IA contract from TJ's fidelity
  review (Manage/mailbox-gear/avatar), surface index of the black-box
  reference, module-extensibility + inference-ban rules, deliberate
  deviations, better-than deltas (module directory search)
- agents-and-auth.md draft.5: mailbox-access semantics pinned (admins
  implicit-all, auto-grant-on-create, admin-only grant endpoints);
  conversation-visibility enforcement deferred to multi-mailbox

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…st (HT-54)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements spec §3.4/§6's pinned mailbox-access semantics: AgentStore
auto-grants every existing mailbox to a new Agent (any role, both
createFirstAdmin and createAgent) in the same transaction as the agents
insert, plus listAgentMailboxIds/replaceAgentMailboxAccess (replace-set,
FK-translated invalid_mailbox); MailboxStore gains an unfiltered
listMailboxes for the Permissions roster. Adds the admin-only
GET /api/v1/mailboxes, GET/PUT /api/v1/agents/{id}/mailboxes endpoints,
wired through the router and composition root.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ecfca3d-fb0c-4daf-a8ef-ceb7423e77fc

📥 Commits

Reviewing files that changed from the base of the PR and between 6104325 and af8f069.

📒 Files selected for processing (5)
  • specs/auth/agents-and-auth.md
  • src/api/agents.test.ts
  • src/api/agents.ts
  • src/store/agents.ts
  • src/store/mailboxes.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/store/mailboxes.test.ts
  • src/api/agents.test.ts
  • specs/auth/agents-and-auth.md
  • src/store/agents.ts
  • src/api/agents.ts

📝 Walkthrough

Walkthrough

Mailbox access administration is added across mailbox and agent stores, authenticated API routes, request dispatch, runtime wiring, specifications, seed data, and end-to-end tests. Agents receive existing mailbox grants transactionally, while admins can list mailboxes and replace Agent grant sets.

Changes

Mailbox access administration

Layer / File(s) Summary
Mailbox store and grant persistence
src/store/mailboxes.ts, src/store/agents.ts, src/store/*test.ts
Adds unfiltered mailbox listing and transactional Agent mailbox-grant creation, replacement, validation, ordering, and rollback behavior.
API contracts, routing, and handlers
src/api/agents.ts, src/api/router.ts, src/api/index.ts, specs/auth/agents-and-auth.md, specs/ui/admin-ia.md, src/api/router.test.ts
Adds admin-only mailbox roster and Agent grant endpoints with UUID validation, response mapping, route matching, acting-Agent checks, and API specification updates.
Runtime wiring and integration validation
scripts/dev-api.ts, src/composition/root.ts, src/dev/seed.ts, src/api/agents.test.ts, src/api/index.test.ts, src/api/gmail-webhook.test.ts, src/mail/gmail-reconcile.test.ts
Provides MailboxStore to API construction, seeds a demo mailbox, and updates test doubles and endpoint coverage for the expanded dependency contract.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Router
  participant InboxAPI
  participant MailboxHandlers
  participant AgentStore
  participant MailboxStore
  Client->>Router: Request mailbox endpoint
  Router->>InboxAPI: Return mailbox route match
  InboxAPI->>MailboxHandlers: Resolve acting Agent and dispatch
  MailboxHandlers->>AgentStore: Read or replace Agent grants
  MailboxHandlers->>MailboxStore: List or validate mailboxes
  MailboxHandlers-->>Client: Return response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main changes: mailbox-access grants, admin API routes, and the new admin IA fidelity doc.
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 feat/ht-54-mailbox-access-engine

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

…(HT-54)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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: 6

🤖 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 `@specs/auth/agents-and-auth.md`:
- Around line 158-173: Update the stale Permissions section introduction around
the “behavior deferred” wording to state that grant management, the Permissions
UI, and mailbox-grant data are shipped and active now; only
conversation-visibility enforcement remains deferred until conversations include
mailbox_id. Remove claims that scoping behavior/UI is absent or that nothing
reads or writes the grants table, while preserving the later semantics and
enforcement plan.

In `@src/api/agents.test.ts`:
- Around line 1214-1240: The test “an empty array clears every grant — 200” must
verify persistence rather than only the response body. After clearing the
mailbox grants, issue another PUT/readback request through the same API flow and
assert the returned mailboxIds is empty, confirming the existing grant was
deleted from storage.
- Around line 1073-1088: In the mailbox roster test, remove the duplicate const
body declaration so the test compiles, then create a disconnected mailbox using
the mailboxStore API before requesting /api/v1/mailboxes. Update the expected
statuses in the test around the includes disconnected/paused mailboxes case to
assert active, paused, and disconnected entries.

In `@src/api/agents.ts`:
- Around line 143-164: Update validateMailboxIds so each UUID is normalized to
lowercase before membership checks and insertion into seen and ids. Preserve
validation of the original string with isUuid, and ensure the returned
deduplicated array contains lowercase UUIDs in first-occurrence order.

In `@src/store/agents.ts`:
- Around line 621-624: Update the grant-replacement transaction’s agent
existence query to lock the matching row for update before processing mailbox
replacements. Keep the not_found result when no row is locked, and perform this
check before both empty and nonempty replacement paths so deleteAgent() cannot
race the operation or cause agent foreign-key errors to be classified as
invalid_mailbox.

In `@src/store/mailboxes.test.ts`:
- Around line 523-540: Update the listMailboxes test to insert a mailbox with
status needs_reconnect, include its ID in the expected sorted ID set, and retain
the existing active, paused, and disconnected cases so every lifecycle status is
verified.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e0e0403-8521-4dd2-b610-d80f86bd33f6

📥 Commits

Reviewing files that changed from the base of the PR and between 9243582 and 4f574cb.

📒 Files selected for processing (16)
  • scripts/dev-api.ts
  • specs/auth/agents-and-auth.md
  • specs/ui/admin-ia.md
  • src/api/agents.test.ts
  • src/api/agents.ts
  • src/api/gmail-webhook.test.ts
  • src/api/index.test.ts
  • src/api/index.ts
  • src/api/router.test.ts
  • src/api/router.ts
  • src/composition/root.ts
  • src/mail/gmail-reconcile.test.ts
  • src/store/agents.test.ts
  • src/store/agents.ts
  • src/store/mailboxes.test.ts
  • src/store/mailboxes.ts

Comment thread specs/auth/agents-and-auth.md
Comment thread src/api/agents.test.ts Outdated
Comment thread src/api/agents.test.ts
Comment thread src/api/agents.ts
Comment thread src/store/agents.ts
Comment on lines +621 to +624
const agentRows = await tx.query<{ id: string }>('SELECT id FROM agents WHERE id = $1', [
agentId,
])
if (agentRows.length === 0) return 'not_found'

@coderabbitai coderabbitai Bot Jul 19, 2026

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 | 🟡 Minor | ⚡ Quick win

Lock the target Agent row during grant replacement.

The unlocked existence check can race with deleteAgent(). An empty replacement may return 'ok' after deletion, while a nonempty replacement can hit the agent_id FK and be misclassified as 'invalid_mailbox' because every 23503 is accepted.

Proposed fix
-        const agentRows = await tx.query<{ id: string }>('SELECT id FROM agents WHERE id = $1', [
-          agentId,
-        ])
+        const agentRows = await tx.query<{ id: string }>(
+          'SELECT id FROM agents WHERE id = $1 FOR UPDATE',
+          [agentId],
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const agentRows = await tx.query<{ id: string }>('SELECT id FROM agents WHERE id = $1', [
agentId,
])
if (agentRows.length === 0) return 'not_found'
const agentRows = await tx.query<{ id: string }>(
'SELECT id FROM agents WHERE id = $1 FOR UPDATE',
[agentId],
)
if (agentRows.length === 0) return 'not_found'
🤖 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 `@src/store/agents.ts` around lines 621 - 624, Update the grant-replacement
transaction’s agent existence query to lock the matching row for update before
processing mailbox replacements. Keep the not_found result when no row is
locked, and perform this check before both empty and nonempty replacement paths
so deleteAgent() cannot race the operation or cause agent foreign-key errors to
be classified as invalid_mailbox.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied in af8f069, via FK discrimination rather than a row lock: isMailboxFkViolation now requires the mailbox_id constraint (driver constraint field, message fallback) since both of the table's FKs raise 23503, and an agent_id FK violation (target hard-deleted between check and INSERT) maps to 'not_found' — the correct caller outcome — instead of a bogus 'invalid_mailbox'. A FOR UPDATE lock would serialize every grant write against agent deletion for a race whose only requirement is a correct response code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@zaridan, the FK discrimination is a good correction for the non-empty path: an agent_id FK violation now maps to 'not_found' instead of incorrectly becoming 'invalid_mailbox'.

However, the empty replacement case remains unresolved. After the unlocked existence check, deleteAgent() can delete the target before this transaction commits; because mailboxIds.length === 0 bypasses the INSERT and therefore has no FK violation to translate, this method still returns 'ok' for a no-longer-existing Agent.

SELECT ... FOR UPDATE is needed if the contract requires the result to reflect a stable target-Agent existence check throughout the replacement transaction. The extra serialization is limited to concurrent deletion of that same Agent, not all grant writes.


✏️ Learnings added
Learnt from: zaridan
Repo: Helpthread/helpthread PR: 72
File: src/store/agents.ts:639-642
Timestamp: 2026-07-19T00:58:18.152Z
Learning: In `src/store/agents.ts`, `agent_mailbox_access` has both `agent_id` and `mailbox_id` foreign keys that can raise SQLSTATE `23503`; `isMailboxFkViolation` must identify the `mailbox_id` constraint specifically so a concurrent target-Agent deletion maps to `ReplaceMailboxAccessResult` `'not_found'` rather than `'invalid_mailbox'`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread src/store/mailboxes.test.ts Outdated
- validateMailboxIds lowercases uuids before dedupe/storage (a mixed-case
  duplicate would collide with the PK inside the store's single INSERT)
- isMailboxFkViolation requires the mailbox_id constraint (both FKs on
  agent_mailbox_access raise 23503); an agent_id FK violation now maps to
  not_found instead of a bogus invalid_mailbox/500
- spec §3.4 heading updated (grants managed now; only enforcement deferred)
- tests: disconnected mailbox actually created in the roster test;
  needs_reconnect added to the all-status test; PUT-clear re-reads via GET

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zaridan
zaridan merged commit c487ba2 into main Jul 19, 2026
5 checks passed
@zaridan
zaridan deleted the feat/ht-54-mailbox-access-engine branch July 19, 2026 01:39
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