Skip to content

feat(store,api): internal notes — direction 'note' + POST /conversations/{id}/notes (HT-28) - #29

Merged
zaridan merged 1 commit into
mainfrom
feat/ht-28-internal-notes
Jul 12, 2026
Merged

feat(store,api): internal notes — direction 'note' + POST /conversations/{id}/notes (HT-28)#29
zaridan merged 1 commit into
mainfrom
feat/ht-28-internal-notes

Conversation

@zaridan

@zaridan zaridan commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Why

Fifth v1.1 increment (HT-28) — internal notes, the warn-tinted bands in the designed UI. Spec: agent-inbox-v1.md §4c (v1.1, #24).

What

Migration 007 — two constraint swaps that must ship as a pair: the direction CHECK widens to admit 'note', and threads_delivery_status_by_direction gets a note arm requiring NULL (the old constraint satisfied neither arm for a note and would have rejected every note row). No backfill; pre-007 rules verified intact after the swap.

StoreappendThread is now note-aware: a note bumps updatedAt (it's activity; the conversation resurfaces) but never reopens a closed/spam conversation (it is not the customer coming back). direction unions widen across NewThread/StoredThread.

APIPOST /conversations/{id}/notes, body { text } (1–5000, plain text in v1) → 201 with the note ThreadView (direction: 'note', from = support address, deliveryStatus: null). Generic 404 for missing/deleted/non-UUID; 405 Allow: POST; 401.

The mail boundary (charter invariant #5 adjacency) — spec §4c says a note reaching the send path is a bug, so it's test-asserted from both sides: the handler never touches sendReply (the fake sender records zero sends), and an aged note is invisible to listDeliverableThreads even at staleAfterMs: 0.

Evidence

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for posting internal notes to conversations.
    • Notes appear as a distinct thread type and are never sent by email.
    • Notes can be added to closed conversations without reopening them.
    • Adding a note updates the conversation activity timestamp.
  • Validation

    • Added validation for note content, request format, conversation IDs, authentication, and supported methods.
  • Data Integrity

    • Notes cannot be assigned email delivery statuses and are excluded from outbound delivery.

…versations/{id}/notes (HT-28)

A note is Agent-only context on a conversation: never emailed, no
token minted, no outbox row, invisible to the delivery worker.

Migration 007 swaps two constraints together (they must ship as a
pair): the direction CHECK widens to admit 'note', and the
delivery-status-by-direction CHECK requires notes to carry NULL like
inbound — the old constraint would have rejected every note row.

Store: appendThread's reopen policy is note-aware — a note on a
closed/spam conversation bumps updated_at (it IS activity) but never
reopens (it is not the customer coming back). The mail boundary is
test-asserted: an aged note is invisible to listDeliverableThreads
even at staleAfterMs 0, and the API test proves the sender is never
invoked (charter invariant #5 adjacency, spec §4c).

API: POST /conversations/{id}/notes — {text} 1-5000, plain text in
v1; 201 with the note ThreadView (direction 'note', from = support
address, deliveryStatus null).

377/377 tests. Per specs/api/agent-inbox-v1.md §4c (v1.1, HT-25).

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

coderabbitai Bot commented Jul 12, 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: 392cdc39-46f8-4fd8-b6a2-5bad675b97c2

📥 Commits

Reviewing files that changed from the base of the PR and between 05e4108 and 7b23a81.

📒 Files selected for processing (8)
  • src/api/conversations.ts
  • src/api/index.test.ts
  • src/api/index.ts
  • src/api/router.ts
  • src/db/migrate.test.ts
  • src/db/migrate.ts
  • src/store/conversations.test.ts
  • src/store/conversations.ts

📝 Walkthrough

Walkthrough

Adds internal note threads with database constraints, store behavior, and a new authenticated POST /api/v1/conversations/{id}/notes endpoint. Notes update activity timestamps without reopening conversations, require null delivery status, and never enter outbound email delivery.

Changes

Internal note thread persistence

Layer / File(s) Summary
Note thread persistence and status rules
src/db/migrate.ts, src/db/migrate.test.ts, src/store/conversations.ts, src/store/conversations.test.ts
Adds migration 007 for note direction and null delivery status, extends store types, preserves closed status while updating activity, and excludes notes from delivery eligibility.

Notes API

Layer / File(s) Summary
Notes route and handler
src/api/router.ts, src/api/index.ts, src/api/conversations.ts
Adds route matching and dispatch for POST /api/v1/conversations/{id}/notes, validates UUIDs and plain-text bodies, appends note threads, and returns the created thread view.
Notes endpoint validation coverage
src/api/index.test.ts
Tests successful creation, closed-conversation behavior, validation failures, missing conversations, method routing, authentication, and the absence of outbound email sends.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant matchRoute
  participant handlePostNote
  participant ConversationStore
  Client->>matchRoute: POST /api/v1/conversations/{id}/notes
  matchRoute-->>handlePostNote: conversation-note route and id
  handlePostNote->>ConversationStore: appendThread(direction: note)
  ConversationStore-->>handlePostNote: created note thread
  handlePostNote-->>Client: 201 thread view
Loading

Possibly related PRs

🚥 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 clearly summarizes the main change: adding internal notes support, including the new note direction and POST notes endpoint.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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-28-internal-notes

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

@zaridan
zaridan merged commit b746919 into main Jul 12, 2026
5 checks passed
@zaridan
zaridan deleted the feat/ht-28-internal-notes branch August 2, 2026 19:19
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