Slice 3: Activity timeline (contacts + organizations) - #2
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
crm_activitytable (003_activities). Polymorphic soft subject:subject_type ENUM(contact|organization|deal)(dealis reserved for the deals slice — the service rejects it until then),kindENUM,body,occurred_at,author; index on(subject_type, subject_id, occurred_at). Append-only (noupdated_at).Activitiesservice —subject_typeis a write-time allow-list (never interpolated) that also selects the table thesubject_idmust exist in;kindallow-listed;bodylength-capped;occurred_atparsed strictly (accepts adatetime-localvalue);authoris server-set (the MCP token name) and never read from client fields, so it can't be spoofed.crm_activities/crm_activity_add/crm_activity_delete, gated on the wildcard-immunenimbuscms.crmcapability;addrecords the token name as author.activity-add/activity-deletepair per page (each inherits the page'scrm:write+ CSRF). No spoofable author field in the admin.Tests
ActivitiesTest(allow-listed subject_type incl.dealrejected, 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).ContactsTest/OrganizationsTestcascade 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.crmcapability. 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