Skip to content

Slice 3: Activity timeline (contacts + organizations) - #2

Merged
DanMat merged 1 commit into
mainfrom
feat/activities
Sep 4, 2026
Merged

DanMat merged 1 commit into
mainfrom
feat/activities

Conversation

@DanMat

@DanMat DanMat commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Third CRM slice: the activity timeline — dated, typed entries (note / call / email / meeting / other) logged against a contact or an organization. Same discipline as Slices 1–2, with the polymorphic-subject sharp edges handled at write.

What's here

  • Schema — crm_activity table (003_activities). Polymorphic soft subject: subject_type ENUM(contact|organization|deal) (deal is reserved for the deals slice — the service rejects it until then), kind ENUM, body, occurred_at, author; index on (subject_type, subject_id, occurred_at). Append-only (no updated_at).
  • Activities service — subject_type is a write-time allow-list (never interpolated) that also selects the table the subject_id must exist in; kind allow-listed; body length-capped; occurred_at parsed strictly (accepts a datetime-local value); author is server-set (the MCP token name) and never read from client fields, so it can't be spoofed.
  • Total-delete cascade — deleting a contact now also removes its activities (the "forget" leaves nothing behind); deleting an org removes the org's own timeline but keeps its people and their activities. Both atomic.
  • MCP — crm_activities / crm_activity_add / crm_activity_delete, gated on the wildcard-immune nimbuscms.crm capability; add records the token name as author.
  • Admin — an inline timeline (list + log form + per-entry delete) on the contact and organization edit pages, sharing one activity-add/activity-delete pair per page (each inherits the page's crm:write + CSRF). No spoofable author field in the admin.
  • Guide — activities section.

Tests

  • New: ActivitiesTest (allow-listed subject_type incl. deal rejected, subject-must-exist, kind allow-list, occurred_at parsing, author-not-over-postable, delete, ordering), ActivitiesAdminTest (escapes hostile body/author, CSRF + subject in forms, nonce'd style).
  • Extended: ContactsTest / OrganizationsTest cascade tests; CrmToolsetTest (activity tools listed, content token can't reach them, author = token name, missing-subject-as-data). All setups build + truncate the activity table.

Security posture

Every activity surface gates on the wildcard-immune nimbuscms.crm capability. Subject link is a bound, allow-listed, existence-checked soft ref — no injection, no dangling rows. Author is un-spoofable. Store-raw/escape-on-render (XSS tests). Delete leaves no residue.

cs-fixer + PHPStan level 6 green locally (borrowed-vendor); phpunit runs in CI.

🤖 Generated with Claude Code

Adds the CRM timeline — dated, typed entries (note/call/email/meeting)
logged against a contact or an organization.

- Schema: crm_activity table + 003_activities migration. Polymorphic
  soft subject: subject_type ENUM(contact|organization|deal) — 'deal'
  reserved for the deals slice; kind ENUM; body/occurred_at/author;
  index on (subject_type, subject_id, occurred_at). Append-only.
- Activities service: subject_type is a write-time allow-list (never
  interpolated) that also picks the table the subject_id must EXIST in;
  kind allow-listed; body length-capped; occurred_at parsed strictly
  (accepts datetime-local); author is server-set (the MCP token name),
  never a client field, so it can't be spoofed.
- Total-delete cascade: deleting a contact now also removes its
  activities (the "forget" leaves nothing behind); deleting an org
  removes the org's own timeline but keeps its people and theirs — both
  atomic (transactions).
- MCP: crm_activities / _add / _delete, gated on the same wildcard-immune
  nimbuscms.crm capability; add records the token name as author.
- Admin: an inline timeline block (list + log form + delete) on the
  contact and organization edit pages, sharing one activity-add /
  activity-delete pair per page (each inherits crm:write + CSRF). No
  spoofable author field in the admin.
- Guide: activities section.
- Tests: ActivitiesTest, ActivitiesAdminTest; Contacts/Organizations
  cascade tests; CrmToolset activity tools + gating + author-not-
  over-postable; all setups build+truncate the activity table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DanMat
DanMat merged commit 32b4b42 into main Sep 4, 2026
2 checks passed
@DanMat
DanMat deleted the feat/activities branch September 4, 2026 16:18
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