Repository navigation
fix(profiles, docs): reps read and join the Discussion of records they can read; the expiry and renewal sweeps run where scheduled work is on; the automation page names the shipped templates (#2029 items 3, 5, 6) - #2043
Merged
Merged
Conversation
A sales rep's record page said "You don't have permission to view comments on this record" and the same for its activity: `sys_comment` and `sys_activity` answered 403 PERMISSION_DENIED to every read and to a post, because the platform's member baseline names neither and no permission set of this app granted them, although the `enable.feeds` note said any member could read and post on any thread. Every set that grants `sys_attachment` now also grants `sys_comment` read, create and edit and `sys_activity` read; `guest_portal` grants neither. Which threads they reach stays with the platform: plugin-audit narrows both reads to rows whose parent the caller can read, refuses a post to a thread whose record the caller cannot read (RECORD_NOT_ACCESSIBLE), and lets only a comment's author edit it. Measured as na.rep on a fresh dev box. The delete bit is held back: its gate is author-or-canEdit(parent), and on 17.7.0 that answers true for every controlled_by_parent parent, so na.rep deleted the admin's comment on a contract whose own PATCH answers them 403. That is reported upstream from #2029. `test/record-comments-access.test.ts` measures both halves on a quote the rep reaches and one they do not; `test/collaboration-capabilities.test.ts` pins the grant shape on every set. Claude-Session: https://claude.ai/code/session_018Mk4tab2eCyY41UTWK7y7V Co-authored-by: Claude <noreply@anthropic.com>
…ork is on; the app ships its notification templates
The Quotes page said quotes are marked Expired every night at 1 AM, in
What happens automatically, Standard list views and Tips for admins, and
the Contracts page said the same of the midnight expiry sweep and the 8 AM
renewal reminder. A default boot prints that `quote_expiration`,
`contract_expiration` and `contract_renewal` declare a schedule trigger
but are NOT bound, because OS_AUTOMATION_SCHEDULED_WORK_ENABLED is unset.
Each passage now states the condition, says what a default install does
instead, and links Administration > Automation's Scheduled automation
section, which documents the switch. The Chinese headings of that section
carry the explicit `scheduled-automation` anchor the links use.
Administration > Automation's Email templates section said HotCRM ships
no email templates and that each notification's wording is written
inline in its flow. Every notify node names one of the templates the app
authors under `src/{sales,service,revenue}/email-templates`, in four
languages. The section now says so in the wording the email-and-calendar
guide uses, and points the merge-field advice at those templates.
Claude-Session: https://claude.ai/code/session_018Mk4tab2eCyY41UTWK7y7V
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Mk4tab2eCyY41UTWK7y7V Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This was referenced Oct 9, 2026
This was referenced Oct 9, 2026
…k is on The in-product package docs stated every scheduled sweep with no condition: the Administration guide's knobs table (stalled-deal 07:30, quote expiry 01:00, case SLA hourly, renewal reminder 08:00, contract expiry 00:00), the Service guide's hourly SLA sweep, and the Sales guide's stalled-deal nudge, quote expiry and the two contract jobs. As na.rep, GET /api/v1/meta/doc/crm_sales served "A daily 01:00 job marks any still-open quote past its expiration date as Expired" on a box whose boot logs quote_expiration NOT bound. Each statement now carries the condition and says what a default install does instead; every time is unchanged, so test/docs-drift.test.ts still reads them. The overview's "a case that misses its SLA escalates on its own" gets the same clause. The zh-Hans and zh-Hant email-and-calendar pages now link the Automation page's scheduled-automation anchor added earlier on this branch. Claude-Session: https://claude.ai/code/session_018Mk4tab2eCyY41UTWK7y7V Co-authored-by: Claude <noreply@anthropic.com>
zhuangjianguo
pushed a commit
that referenced
this pull request
Oct 9, 2026
…e-defects Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Mk4tab2eCyY41UTWK7y7V
This was referenced Oct 9, 2026
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.
Fixes #2029
Clause-②: no
Items 3, 5 and 6 of the card. Items 1, 2 and 4 landed in #2036 (
ac162c9b). Each item was measured on a freshobjectstack devbox (@objectstack/*17.7.0, hotcrmac162c9b, port 48213, staffed bypnpm demo:staff) before anything was changed.Item 5: a rep's Discussion panel refused comments and activity
Measured before. As
na.rep(sales_rep+na_sales_team;/auth/me/permissionslistssales_rep,member_default), every read ofsys_comment,sys_activity(andsys_audit_log) answered 403PERMISSION_DENIED, on records the rep can read and on records they cannot. A post to a thread answered the same 403. The record page says "You don't have permission to view comments on this record." and the same for its activity (objectuiRecordDetailViewreads exactly these two objects for the panel).Producer: the app's grants. The platform's member baseline names neither object (it is explicit-allow since objectstack-ai/objectstack#5491), and no permission set of this app granted them. Which threads a grant reaches is the platform's:
plugin-audit17.7.0 narrows everysys_commentandsys_activityread to rows whose parent the caller can read (engine middleware, by the caller's own read of the parent), refuses a post to a thread whose record the caller cannot read, and lets only a comment's author edit it (the platform's created_by update floor).Security floor, measured with a temporary full CRUD grant on
sales_rep(never committed): parents were an account, a quote and a contract the rep reads (Acme) and an account, a quote and a contract the rep cannot read (Northwind; the quote and contract were created on the throwaway box).RECORD_NOT_FOUNDRECORD_NOT_ACCESSIBLEPERMISSION_DENIED(row-level floor)The platform's gate never let the rep reach the comments or activity of a record they cannot read. One limb is wider than the platform's own declared rule ("only its author or a user who can edit the parent record may delete it"):
na.rep'sPATCHof the Acme contract answers 403, yet theirDELETEof the admin's comment on that contract answered 200 and the comment was gone. The delete gate asks the sharing service'scanEdit(parent), the call objectstack-ai/objectstack#22455 reports for attachments, and it answerstruefor everycontrolled_by_parentparent. So the delete bit is not granted (AGENTS.md §2: wait for the platform, do not route around it). It is the same shape as item 1's write half, which waits as #2038.Fix. The five sets that grant
sys_attachment(sales_rep,sales_manager,marketing_user,service_agent(whichservice_managerspreads),system_admin(whichtenant_adminspreads)) now also grantsys_commentread + create + edit andsys_activityread.guest_portalnames neither. Theenable.feedsparagraph of the canonical note insrc/sales/objects/index.tsis rewritten to the measured facts. It used to say that "any authenticated org member can read and post on any thread", which is no longer true on 17.7.0.Measured after, same box and same fixture: comments and activity of the readable account, quote and contract are listed (by id 200). Those of the unreadable ones are not (0 rows, 404 by id). Posting gives 201 vs 403
RECORD_NOT_ACCESSIBLE. The rep edits their own comment (200) and not the admin's (403). Deleting the admin's comment answers 403PERMISSION_DENIEDon all six parents. In Chromium asna.rep, the record page of QTE-0001 (/_console/apps/app.objectstack.hotcrm/crm_quote/record/8pWCceRNqqwqEuNF) renders the comments ("admin note …", "rep note …" in the page text). Neither "view comments on this record" nor "view activity on this record" appears.Item 3: the Quotes and Contracts pages state the sweeps unconditionally
Measured. A default boot of this tree (no
OS_AUTOMATION_SCHEDULED_WORK_ENABLEDin the environment) logsflow 'quote_expiration' declares a 'schedule' trigger but is NOT bound … OS_AUTOMATION_SCHEDULED_WORK_ENABLED is unset or not truthy, and the same forcontract_expiration,contract_renewaland the other five scheduled flows (8 of 8). The Quotes page (three locales) says quotes expire every night at 1 AM in Built-in rules, What happens automatically, Standard list views and Tips for admins, and calls the renewal sweep daily. The Contracts page (three locales) says the same of the midnight expiry sweep and the 8 AM renewal reminder (the English page words it "Each night at midnight" and "At 8 AM", which is why a grep for "every night" missed it).Fix. Each passage now states the condition, says what a default install does instead, and links Administration › Automation's Scheduled automation section, which documents the switch. A lapsed quote or an activated contract past its end date keeps its status until someone changes it, and no renewal task, notification or deal is created. The Renewal Calendar tab and the End Date order of All Contracts still show a renewal coming. The schedule times themselves (1 AM, midnight, 8 AM) are unchanged and still match the flows (
test/docs-drift.test.ts,test/automation-docs-coverage.test.ts). The zh-Hans and zh-Hant headings of that section now carry the explicit anchor idscheduled-automation(AGENTS.md, Documentation discipline rule 6), which the new links use.Item 6: the automation page says the app ships no email templates
Measured. As admin on the same box,
sys_email_templateserves 76crm.*rows: 19 names inen-US,es-ES,ja-JPandzh-CN. All 19notifynodes undersrc/*/flowsname one of them (template: 'crm.…'). The page's Email templates section said "HotCRM itself ships no email templates" and that each notification's subject and body are written inline in its flow.Fix. That paragraph now uses the wording the email-and-calendar guide took in item 4: the kinds of alert, the four languages, the per-recipient language, and where the templates live in the source. It adds that the wording is changed in the template rather than the flow, and that alerts from scheduled flows go out only where scheduled work is on. The merge-field bullet no longer says there is nothing to copy. It now points at those templates' flat
{{name}}-style holes, which their notify node fills. All three locales are changed. The section names no count, following AGENTS.md rule 5.Verification
Final head
cffe8c4:OS_VERIFY_LOCK_SLOT=hotcrm-2029 os-verify-lock.sh -c 'pnpm verify'printedos-verify-lock: VERDICT command-exit 0. The steps reported:src/salesauthored total ~101,244 of ~107,000)test/record-comments-access.test.tsruns on the shared boot, which carriesAuditPlugin. A comment and an activity row sit on a quote the rep reaches throughnorth_america_territoryand on one they do not reach. The test pins five things:RECORD_NOT_ACCESSIBLEon the second;PERMISSION_DENIED;test/collaboration-capabilities.test.tspins the grant shape on every set that reads a record, and thatguest_portalnames neither object.ff6b059, through objectstackscripts/ablation-replace.mjs. Each anchor went from 1 hit to 0, the blob changed, and the restore was verified as blob == HEAD with an emptygit diff HEAD:sales_rep'ssys_commentline turned 3 tests red with 403PERMISSION_DENIED: the list, the post, and the structural test namingsales_rep → sys_comment(3 failed | 12 passed).sales_rep'ssys_activityline turned the activity case red with 403 (1 failed | 5 passed).test/docs-*.test.ts, automation coverage, sharing coverage, state machines) pass: 29 files, 647 tests, the anchor-link audit included.Acceptance notes
plugin-securitysays it leaves the edit limb under the floor deliberately.sys_audit_logis still refused to reps (403, measured). Reading objectui'sRecordDetailViewsource (not measured in a browser), the History tab turns that refusal into an empty list rather than a refusal message. It is not in this item's measured statement, and the profile files are also edited by docs(src): repoint the source comments that cite pre-ADR-0130 directories #2040 ([finding] 26 source comments in src/ cite src/flows/ — a directory ADR-0130 removed (blocked on Track A: file surface collides) #1919), so it is listed for the seat instead of widened here.mainand rerunspnpm verify.Patch round 1
Head
0a2afd0. It mergesmainat4fbfde1, which brings #2041 and #2040 (#1919). Both merges were clean, with no hand resolution, no rebase and no force-push. #2040's hunks on the shared files (the flows sentence above the knobs table insrc/docs/crm_admin.md, and path repoints in profile comments) cover none of this PR's lines, and both sides are kept as written.Item 3, finished on the in-product guides (
src/docs, compiled into the artifact'sdocs[]). Each sweep these guides name now carries the scheduled-work condition and says what a default install does instead. Every time is unchanged, sotest/docs-drift.test.tsstill reads them.crm_admin.md: the five swept rows of the knobs table are marked scheduled work only, and a paragraph under the table names the switch and what an unbound install does. The flows sentence above the table (docs(src): repoint the source comments that cite pre-ADR-0130 directories #2040's) is untouched.crm_service.md: the hourly SLA sweep and the "never catch a missed SLA by hand" line.crm_sales.md: the stalled-deal nudge, the quote expiry, and the two contract jobs. This file was not in the amended list, butGET /api/v1/meta/doc/crm_salesis the door the finding measured, so it is fixed here as a bounded in-place fix: same defect class, the same sentence form, no other claim holds the file, and no new gate.crm_overview.md: "a case that misses its SLA escalates on its own" gets the same clause, as the same bounded fix.Measured after a rebuild. On a fresh dev box with no scheduled-work variable set, the boot logs 8 of 8 scheduled flows NOT bound.
GET /api/v1/meta/doc/crm_sales,crm_service,crm_adminandcrm_overviewall answer 200 and serve the new condition sentences. Neither the old "A daily 01:00 job marks…" nor "An hourly sweep checks every open case." is served any more. This was read asna.reponf3b5cf4and again as admin on0a2afd0.Item 4's links. The zh-Hans and zh-Hant email-and-calendar pages now link
/zh-*/docs/administration/automation#scheduled-automation.Changeset. One sentence is added for the in-product guides.
Verification. On
0a2afd0,pnpm verifyprintedos-verify-lock: VERDICT command-exit 0. The steps reported:src/salesauthored total ~101,252 of ~107,000)Seat note: the two guides beyond the amended list (
crm_sales.md,crm_overview.md) are accepted as a bounded in-place fix: the same defect and sentence form, andGET /api/v1/meta/doc/crm_salesis the door the finding measured.Generated by Claude Code