Skip to content

feat(contacts): cover full public API for MCP, move logic into business services - #1093

Merged
realcodesiman merged 6 commits into
mainfrom
feat/contacts-public-api-mcp
Sep 8, 2026
Merged

feat(contacts): cover full public API for MCP, move logic into business services#1093
realcodesiman merged 6 commits into
mainfrom
feat/contacts-public-api-mcp

Conversation

@realcodesiman

@realcodesiman realcodesiman commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds the remaining contacts public-API surface — bulk operations, export/export-file status, refresh-profile, and notes/sequences/inboxes/filter-fields (each as its own feature) — so MCP and other workspace-token callers can fully manage contacts, not just read/write the core fields.
  • Moves logic that previously lived only inside server-action inner functions (create/update/delete contact, tag attach/detach/replace, custom-field apply, message send, contact import) into packages/business services and packages/database repositories, so the public API handler and the private server action call the exact same code path instead of diverging copies.
  • Fixes two real bugs surfaced by that duplication: refresh-profile returned 401 for token callers because the old code path required a browser session; contacts_createNotecontacts_listNotes in the same MCP session could see a stale note because nothing invalidated the notes cache on write.

Changes

  • New public routers: contact-notes, contact-sequences, contact-inboxes, contact-filter (each with its own api/public.ts + schema/public.ts), composed into features/contacts/api/public.ts; plus contacts/api/public/{bulk,export,refresh-profile,tags,custom-fields}.ts.
  • New business services/repos: contact/create-with-inbox.ts, contact/update-fields.ts, contact-export/service.ts, message/create-outgoing.ts, database/repositories/contact/, database/repositories/media-library-file/.
  • Server actions (create-contact, delete-contact, add/remove/update-contact-tag, add-contact-custom-field, update-contact-field, create-message, create-tag, delete-tag, import-contacts, create-webhook, create-flow, create-trigger, create-automated-response) now delegate to the shared business service instead of holding their own copy of the logic.
  • Removed dead code: features/contacts/server/** (legacy ad-hoc business layer), queries/public-find-contact.ts, folders/actions/utils.ts, unused tag store provider files.
  • New Drizzle migration adding contact email/phone/workspace indexes (packages/database/drizzle/20260905151444_...).
  • Doc updates: .agents/skills/{orpc-api,feature-scaffold}/SKILL.md (submodule composition pattern, server/ directory ban), docs/developer/workspace-api-tokens.md (accurate endpoint-to-scope table and test list).

Follow-up: layering audit (latest commit)

Audited every change on this branch against the action | API handler → service → repository → DB model and closed the remaining gaps:

  • Contacts list/count and messages queries move into contactService/messageService, with repositories reduced to raw where-builders (list-contacts.queries.ts and the messages query adapter both shrink to thin request adapters).
  • Public and private handlers converge on the same service methods: contactService.list, conversationService.resolveContactInboxForConversation, contactCustomFieldService.applyOperations.
  • triggerService.create, webhookService.create, and flowService.createDraft replace direct db use in the corresponding create actions.
  • The worker's apps/worker/src/integration/handlers/contact.ts flow-step handlers now delegate to contactService.update, contactNoteService.create, tagService.attachByNamesToContacts/detachByNamesFromContacts (extended with an optional contactInbox param to preserve the worker's precise per-inbox ads-conversion enqueue), and a new contactSequenceService.enrollFromFlow. subscribeBroadcast/unsubscribeBroadcast intentionally keep their direct db writes — swapping them would have silently dropped an idempotency guard and an unsubscribe event.
  • Rewrote .agents/rules/data-access.md and 8 skills (business-data-access, feature-scaffold, orpc-api, integration-channel, security-review, testing-workflow, chatbotx-basecode, contact-filter) plus AGENTS.md/CLAUDE.md/docs/developer/workspace-api-tokens.md to document the layering contract; ran pnpm sync:agent-instructions.
  • Added business-layer test coverage for every moved/new service method (contact-list, contact-repository-list-where, message-list-for-conversation, contact-resolve-id-by-identifier, conversation-resolve-contact-inbox-for-send, plus trigger/webhook/flow/automated-response/tag/sequence coverage).

Test plan

  • pnpm lint
  • pnpm --filter @chatbotx.io/database check-types && test
  • pnpm --filter @chatbotx.io/business check-types && test
  • pnpm --filter builder check-types && test (422 files / 2684 tests green)
  • pnpm --filter worker check-types && test (199 files green; 1 pre-existing unrelated failure confirmed on base branch)
  • invariant-guard agent dispatched on the final diff — no violations found
  • Manual: chatbotx-mcp-servercontacts_refreshProfile, contacts_createNotecontacts_listNotes immediately after, contacts_create with a mismatched inbox channel (expect 400, not 500)

realcodesiman and others added 5 commits September 8, 2026 05:57
…ss services

Adds the remaining contacts public-API surface (bulk ops, export, refresh
profile, notes/sequences/inboxes/filter-fields as their own features) so
MCP and other workspace-token callers can fully manage contacts. In the
process, moves business logic that lived only in server-action inner
functions (create/update/delete contact, tag attach/detach/replace,
custom-field apply, message send, contact import) into
packages/business services and packages/database repositories, so the
public API and the action call the same code path instead of duplicating
it — fixing a refresh-profile 401 caused by a stray session check and a
stale contact-notes cache that never invalidated on write.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…invalidation, close public API gaps

Closes the review findings on the contacts public API/MCP surface: a
workspace-A token could enroll contacts into a workspace-B sequence
(sequential bigint ids, never validated); tag writes invalidated cache tags
nothing ever reads, so renamed/deleted tags and stale contact tag lists
persisted for up to 24h; and the custom-field update path silently dropped
writes to fields 51+ in workspaces with more than 50 custom fields.

Also makes bulk endpoints report skipped ids instead of a blind 204,
surfaces channel-send enqueue failures instead of swallowing them, restores
the sequential (not concurrent) pre-auth rate-limit gate, adds read caching
now that invalidation is correct, and removes dead code left behind by the
original PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ayering

Audits every change on this branch against the action|API handler -> service
-> repository -> DB model and fixes deviations: contacts list/count and
messages queries move into contactService/messageService with repositories
reduced to raw where-builders; public and private handlers converge on the
same service methods (contactService.list, resolveContactInboxForConversation,
contactCustomFieldService.applyOperations); trigger/webhook/flow create
actions and the worker's contact.ts flow-step handlers drop direct `db` use
in favor of triggerService.create, webhookService.create, flowService.createDraft,
tagService.attachByNamesToContacts/detachByNamesFromContacts, and
contactSequenceService.enrollFromFlow. Updates .agents/rules/data-access.md,
8 skills, AGENTS.md, CLAUDE.md, and workspace-api-tokens.md to document the
layering contract, and adds business-layer test coverage for the new/moved
service methods.

subscribeBroadcast/unsubscribeBroadcast intentionally stay on direct db
writes (a service swap would have dropped an idempotency guard and an
unsubscribe event) - follow-up, not an oversight.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Umbodvf34JwbqNQsSGt7ZR
…ness barrel

media-library-trigger.tsx ("use client") value-imported
MEDIA_LIBRARY_FILES_PAGE_SIZE re-exported from @chatbotx.io/business,
whose 103-line barrel reaches the Postgres pool and Redis client -
dragging server-only deps into the browser bundle.

- Duplicate the constant into a feature-local constants.ts instead of
  re-exporting it from the backend barrel.
- Add package.json subpath exports (./inbox/schema,
  ./ads-conversion/schema) for the three other schema files that
  value-imported the same barrel for shared zod schemas, so those
  keep a single source of truth without pulling in the barrel.
- Add an AST-based guard test that fails if any "use client" file
  reaches @chatbotx.io/business (even transitively through first-party
  re-exports), mirroring the existing client-imports-no-queries guard.
… for public API

Moves remaining contact, tag, note, and import logic into packages/business
services, tightens public API schemas, and removes dead trigger/webhook
constants superseded by the worker-config schedule constants module.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VWhmQGALFoH4NVXMsXypA
@realcodesiman
realcodesiman force-pushed the feat/contacts-public-api-mcp branch from b4a6220 to b463729 Compare September 7, 2026 22:57
…yword text

Four correctness fixes surfaced while finishing the contacts public API:

- `contactService.list`/`count` now take a required `scope`. The public API
  must pass the explicit `UNSCOPED` literal instead of omitting the argument,
  so member scoping (and PII masking) can never be lost by a forgotten
  parameter on a PII-bearing read.
- `automatedResponseService.create` throws when both `flowId` and `text` are
  given instead of silently nulling `text`. Template install forwards the
  manifest's fields verbatim, so the old behaviour dropped authored content
  with no error surfaced.
- The oRPC error interceptor no longer emits a bare i18n key for a
  `ChatbotXException` carrying `data`; it appends the params so an API
  consumer gets a self-describing message.
- `deleteContactNoteAction` swallows `notFound` so a double-click on the
  optimistic notes list does not raise an error toast. The public API keeps
  its 404.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VWhmQGALFoH4NVXMsXypA
@realcodesiman
realcodesiman merged commit fbb6632 into main Sep 8, 2026
6 of 7 checks passed
@realcodesiman
realcodesiman deleted the feat/contacts-public-api-mcp branch September 8, 2026 01:00
realcodesiman added a commit that referenced this pull request Sep 8, 2026
…oks data access into business

Removes direct db usage from flows, triggers, sequences, broadcasts,
webhooks, the template resource picker and saved replies per
.agents/rules/data-access.md. Public oRPC router keys, paths and the
public-spec snapshot are unchanged.

- new flow, sequence, broadcast, trigger, condition and
  template-selectable-resource repositories; webhook repository gains
  paginated list + detail reads
- flowService.createWithDefaultDraft, flowVersionService.publish,
  triggerService/webhookService create/update/updateSettings/deleteMany
  (trigger and webhook condition-diff semantics kept separate),
  new sequenceService, broadcastService.create/resend,
  savedReplyService.listByWorkspaceId
- broadcastService.create validates each integration id independently
  so the validation error lands on the field that failed
- validationException added to errors.ts in the same form as #1093
- deleteSequence now also scopes by workspaceId (defense in depth)
realcodesiman added a commit that referenced this pull request Sep 8, 2026
…oks data access into business

Removes direct db usage from flows, triggers, sequences, broadcasts,
webhooks, the template resource picker and saved replies per
.agents/rules/data-access.md. Public oRPC router keys, paths and the
public-spec snapshot are unchanged.

- new flow, sequence, broadcast, trigger, condition and
  template-selectable-resource repositories; webhook repository gains
  paginated list + detail reads
- flowService.createWithDefaultDraft, flowVersionService.publish,
  triggerService/webhookService create/update/updateSettings/deleteMany
  (trigger and webhook condition-diff semantics kept separate),
  new sequenceService, broadcastService.create/resend,
  savedReplyService.listByWorkspaceId
- broadcastService.create validates each integration id independently
  so the validation error lands on the field that failed
- validationException added to errors.ts in the same form as #1093
- deleteSequence now also scopes by workspaceId (defense in depth)
realcodesiman added a commit that referenced this pull request Sep 8, 2026
…oks data access into business

Removes direct db usage from flows, triggers, sequences, broadcasts,
webhooks, the template resource picker and saved replies per
.agents/rules/data-access.md. Public oRPC router keys, paths and the
public-spec snapshot are unchanged.

- new flow, sequence, broadcast, trigger, condition and
  template-selectable-resource repositories; webhook repository gains
  paginated list + detail reads
- flowService.createWithDefaultDraft, flowVersionService.publish,
  triggerService/webhookService create/update/updateSettings/deleteMany
  (trigger and webhook condition-diff semantics kept separate),
  new sequenceService, broadcastService.create/resend,
  savedReplyService.listByWorkspaceId
- broadcastService.create validates each integration id independently
  so the validation error lands on the field that failed
- validationException added to errors.ts in the same form as #1093
- deleteSequence now also scopes by workspaceId (defense in depth)
realcodesiman added a commit that referenced this pull request Sep 9, 2026
…oks data access into business

Removes direct db usage from flows, triggers, sequences, broadcasts,
webhooks, the template resource picker and saved replies per
.agents/rules/data-access.md. Public oRPC router keys, paths and the
public-spec snapshot are unchanged.

- new flow, sequence, broadcast, trigger, condition and
  template-selectable-resource repositories; webhook repository gains
  paginated list + detail reads
- flowService.createWithDefaultDraft, flowVersionService.publish,
  triggerService/webhookService create/update/updateSettings/deleteMany
  (trigger and webhook condition-diff semantics kept separate),
  new sequenceService, broadcastService.create/resend,
  savedReplyService.listByWorkspaceId
- broadcastService.create validates each integration id independently
  so the validation error lands on the field that failed
- validationException added to errors.ts in the same form as #1093
- deleteSequence now also scopes by workspaceId (defense in depth)
realcodesiman added a commit that referenced this pull request Sep 9, 2026
… ai-agents, reflinks, ai-triggers (#1102)

* refactor(builder): move flows, triggers, sequences, broadcasts, webhooks data access into business

Removes direct db usage from flows, triggers, sequences, broadcasts,
webhooks, the template resource picker and saved replies per
.agents/rules/data-access.md. Public oRPC router keys, paths and the
public-spec snapshot are unchanged.

- new flow, sequence, broadcast, trigger, condition and
  template-selectable-resource repositories; webhook repository gains
  paginated list + detail reads
- flowService.createWithDefaultDraft, flowVersionService.publish,
  triggerService/webhookService create/update/updateSettings/deleteMany
  (trigger and webhook condition-diff semantics kept separate),
  new sequenceService, broadcastService.create/resend,
  savedReplyService.listByWorkspaceId
- broadcastService.create validates each integration id independently
  so the validation error lands on the field that failed
- validationException added to errors.ts in the same form as #1093
- deleteSequence now also scopes by workspaceId (defense in depth)

* feat(automation): widen public API to full CRUD across flows, triggers, keywords, ai-agents, reflinks, ai-triggers

Finishes the data-access chain (action -> service -> repository -> DB)
for flows, sequences, broadcasts, saved-replies, reflinks, bot-fields,
and greenfield ai-triggers, then widens the `automation` token scope
from read-only to full CRUD so MCP/agent clients can build, publish,
and inspect automations end to end.

* fix(automation): close data-loss and correctness bugs in PR #1102's public API

Remediates review findings from PR #1102's data-access refactor and public
API widening:

- PUT /v1/keywords/{id} without `keywords` silently wiped the automation's
  keywords to `[]`; the service now leaves the column untouched when the
  caller omits it, and moves the text/flowId mutual-exclusion + cross-
  workspace flowId validation down from the action into the service so
  every caller gets the same invariants.
- POST /v1/ai-agents could return the wrong resource on a duplicate name
  (no unique constraint); `create` now returns the inserted id and the
  handler re-fetches by id instead of by name.
- public-spec-operations.test.ts had no explicit `beforeAll` timeout,
  causing CI-only flakiness as the OpenAPI snapshot grew.
- GET /v1/triggers loaded every trigger in the workspace then re-queried
  each one individually; added `triggerService.list` backed by a single
  SQL-paginated query with conditions joined in.
- Reflinks' `findReflink` swallowed every error (including infra failures)
  as not-found; added a nullable `reflinkService.find()`.
- Public flow imports attributed every import to the workspace owner;
  now pass `userId: null`, matching the contacts public-API precedent.
- `triggerResource` published `z.array(z.any())` for conditions; conditions
  now has a real, documented shape.
- `resendBroadcast` read a broadcast's contact filter before verifying it
  was resendable, and dropped the `deletedAt` predicate; added
  `broadcastService.assertResendable` to guard first.
- `updateSequenceAction` masked a 404 as a generic 500 by wrapping the
  whole call in a catch-all; now rethrows `ChatbotXException` unchanged.
- `aiTriggerService.list` divided pageCount by an unclamped `perPage`.

Also: dedicated tests for `webhookService.updateWithConditions`,
`sequence/step-payload.ts`'s defaulting logic, `broadcastService.create`'s
insert shape, and bot-field unique-violation mapping; replaced two
hand-rolled `UNIQUE_VIOLATION_CODE` checks with `isUniqueViolationError`;
collapsed `templateSelectableResourceRepository`'s 11 near-identical list
methods into one generic helper; removed dead double-mapping of trigger/
webhook conditions now that the service owns column normalization.

* fix(automation): restore flows list contract and field-level validation errors

- GET /v1/flows again defaults `active: true` and returns `{id, name}`
  instead of the full flow row plus embedded flowVersions, avoiding a
  breaking change to an existing public-API consumer contract.
- Add isValidationException (a real instanceof ChatbotXException guard)
  and use it in place of duck-typed `"code" in error` checks in reflinks,
  bot-fields, and sequences actions, which could mis-narrow on unrelated
  driver/system errors and always hardcoded the error message.
- Wrap automatedResponseService.update in update-automated-response-action
  so the flowId-not-found validation exception (moved into the service by
  the prior refactor) still surfaces as a field-level form error instead
  of a generic toast.
- Fix aiTriggerService.list to divide pageCount by the same clamped limit
  the repository uses (getPaginationWithDefaults), instead of a
  Math.min(maxLimit, perPage) expression that could divide by undefined.

* fix(automation): scope keyword writes by type and stabilize public API pagination

Third round of correctness fixes on the public API surface:

- Keywords endpoints now pass `type: "inbound"` through findOrFail, update,
  setStatus and deleteMany so an outbound (Page) automated response can no
  longer be read, mutated or deleted through the inbound Keywords routes —
  one table serves two FolderTypes, so workspaceId + id alone is not a
  sufficient scope.
- Add `flowVersionService.updateDraftByFlowId` for callers that only know the
  flow id; `PUT /v1/flows/{id}/draft` was passing a flow id where a
  flow-version id was expected.
- Give `triggerRepository.listPaginatedWithConditions` a deterministic
  `orderBy` so paginated results cannot repeat or skip rows.
- Skip no-op updates in ai-agent, ai-trigger and reflink services so an
  all-undefined payload no longer issues an empty SET or a spurious audit
  entry.
- Write `questions: []` explicitly on ai-trigger create — the column has no
  database default despite the drizzle `.default()`.
- Correct `flowService.list`'s return type to `limit`/`offset`, matching what
  `parsePagination` actually spreads.

* fix(automation): unify trigger/webhook list pagination and drop redundant broadcast query

Closes the remaining data-access gaps from the flows/triggers/sequences/
broadcasts/webhooks refactor: the public API and builder pages for triggers
and webhooks each maintained a separate, diverging list implementation, and
the webhook public API loaded every row in the workspace before paginating
in memory.

- webhookService.list: SQL-paginated, conditions joined, shared by
  GET /v1/webhooks and the builder's webhooks page (replaces the unbounded
  listByWorkspaceId + paginateInMemory path)
- triggerService.list: extended with folderId/name filters so the builder's
  triggers page can share it with GET /v1/triggers instead of hand-rolling
  a second, unordered pagination; added triggerService.findWithConditions
  to remove three direct repository reads from the public API
- resend-broadcast.action: read contactFilter off assertResendable's
  already-fetched row instead of issuing a second query for it
- create-broadcast.action: use isValidationException instead of a
  duck-typed error check, matching every sibling action
- flow detail pages: call flowService.findById instead of flowRepository
  directly

* fix(automation): move audience/list reads and step scheduling into business services

Closes remaining direct-repository access from the builder app layer for
broadcasts (list, audience, findByIdOrName), sequences (list, findWithSteps),
and moves sequence-step contact-schedule recalculation into
packages/business/src/sequence, alongside triggers/ai-agents write handlers
now returning their created/updated model instead of a redundant follow-up
findBy. Automated-response reads/writes are scoped by type end-to-end
(inbound vs outbound) to prevent cross-type leaks.

* fix(automation): move template picker query into business and scope reflink writes

Move the template picker's selectable-resource dispatch out of the builder
query layer into `templateService.listSelectableResources`, so the query file
is a thin adapter and the repository is no longer reached from `apps/`.

Scope `reflinkService` update/deleteMany/listOptions by `type = "refLink"` to
match what `create` stamps, so an entry-point-link row sharing the table can
never be updated or deleted through the reflink surface.

Stop swallowing every error on the flow pages: only a service-thrown
`notFoundException` becomes `notFound()`, and a DB failure propagates as a
real 500 instead of a misleading 404.

Drop the now-unused `broadcastRepository.findIdIfActive` and
`findContactFilter` along with their tests and service mocks.
cursor Bot pushed a commit to JuanBifrost/ChatbotX that referenced this pull request Sep 10, 2026
…ss services (ChatbotXIO#1093)

* feat(contacts): cover full public API for MCP, move logic into business services

Adds the remaining contacts public-API surface (bulk ops, export, refresh
profile, notes/sequences/inboxes/filter-fields as their own features) so
MCP and other workspace-token callers can fully manage contacts. In the
process, moves business logic that lived only in server-action inner
functions (create/update/delete contact, tag attach/detach/replace,
custom-field apply, message send, contact import) into
packages/business services and packages/database repositories, so the
public API and the action call the same code path instead of duplicating
it — fixing a refresh-profile 401 caused by a stray session check and a
stale contact-notes cache that never invalidated on write.

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

* fix(contacts): scope sequence enrollment to workspace, fix tag cache invalidation, close public API gaps

Closes the review findings on the contacts public API/MCP surface: a
workspace-A token could enroll contacts into a workspace-B sequence
(sequential bigint ids, never validated); tag writes invalidated cache tags
nothing ever reads, so renamed/deleted tags and stale contact tag lists
persisted for up to 24h; and the custom-field update path silently dropped
writes to fields 51+ in workspaces with more than 50 custom fields.

Also makes bulk endpoints report skipped ids instead of a blind 204,
surfaces channel-send enqueue failures instead of swallowing them, restores
the sequential (not concurrent) pre-auth rate-limit gate, adds read caching
now that invalidation is correct, and removes dead code left behind by the
original PR.

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

* refactor(contacts): align public API branch with service/repository layering

Audits every change on this branch against the action|API handler -> service
-> repository -> DB model and fixes deviations: contacts list/count and
messages queries move into contactService/messageService with repositories
reduced to raw where-builders; public and private handlers converge on the
same service methods (contactService.list, resolveContactInboxForConversation,
contactCustomFieldService.applyOperations); trigger/webhook/flow create
actions and the worker's contact.ts flow-step handlers drop direct `db` use
in favor of triggerService.create, webhookService.create, flowService.createDraft,
tagService.attachByNamesToContacts/detachByNamesFromContacts, and
contactSequenceService.enrollFromFlow. Updates .agents/rules/data-access.md,
8 skills, AGENTS.md, CLAUDE.md, and workspace-api-tokens.md to document the
layering contract, and adds business-layer test coverage for the new/moved
service methods.

subscribeBroadcast/unsubscribeBroadcast intentionally stay on direct db
writes (a service swap would have dropped an idempotency guard and an
unsubscribe event) - follow-up, not an oversight.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Umbodvf34JwbqNQsSGt7ZR

* fix(media-library): stop client bundle from pulling backend-only business barrel

media-library-trigger.tsx ("use client") value-imported
MEDIA_LIBRARY_FILES_PAGE_SIZE re-exported from @chatbotx.io/business,
whose 103-line barrel reaches the Postgres pool and Redis client -
dragging server-only deps into the browser bundle.

- Duplicate the constant into a feature-local constants.ts instead of
  re-exporting it from the backend barrel.
- Add package.json subpath exports (./inbox/schema,
  ./ads-conversion/schema) for the three other schema files that
  value-imported the same barrel for shared zod schemas, so those
  keep a single source of truth without pulling in the barrel.
- Add an AST-based guard test that fails if any "use client" file
  reaches @chatbotx.io/business (even transitively through first-party
  re-exports), mirroring the existing client-imports-no-queries guard.

* refactor(contacts): finish service-layer alignment and schema cleanup for public API

Moves remaining contact, tag, note, and import logic into packages/business
services, tightens public API schemas, and removes dead trigger/webhook
constants superseded by the worker-config schedule constants module.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VWhmQGALFoH4NVXMsXypA

* fix(contacts): require explicit UNSCOPED opt-out and stop dropping keyword text

Four correctness fixes surfaced while finishing the contacts public API:

- `contactService.list`/`count` now take a required `scope`. The public API
  must pass the explicit `UNSCOPED` literal instead of omitting the argument,
  so member scoping (and PII masking) can never be lost by a forgotten
  parameter on a PII-bearing read.
- `automatedResponseService.create` throws when both `flowId` and `text` are
  given instead of silently nulling `text`. Template install forwards the
  manifest's fields verbatim, so the old behaviour dropped authored content
  with no error surfaced.
- The oRPC error interceptor no longer emits a bare i18n key for a
  `ChatbotXException` carrying `data`; it appends the params so an API
  consumer gets a self-describing message.
- `deleteContactNoteAction` swallows `notFound` so a double-click on the
  optimistic notes list does not raise an error toast. The public API keeps
  its 404.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VWhmQGALFoH4NVXMsXypA

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant