Skip to content

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
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-2029-items-3-5-6
Oct 9, 2026

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

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 fresh objectstack dev box (@objectstack/* 17.7.0, hotcrm ac162c9b, port 48213, staffed by pnpm 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/permissions lists sales_rep, member_default), every read of sys_comment, sys_activity (and sys_audit_log) answered 403 PERMISSION_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 (objectui RecordDetailView reads 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-audit 17.7.0 narrows every sys_comment and sys_activity read 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).

what the rep tried readable parent unreadable parent
list / query the thread's comments 200, the thread's rows 200, 0 rows
read the admin's comment by id 200 404 RECORD_NOT_FOUND
read the record's activity 200, all rows the admin sees 200, 0 rows
post a comment 201 403 RECORD_NOT_ACCESSIBLE
edit their own comment 200 (no comment to edit)
edit the admin's comment 403 PERMISSION_DENIED (row-level floor) 404
delete the admin's comment 200 on account, quote and contract 404

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's PATCH of the Acme contract answers 403, yet their DELETE of the admin's comment on that contract answered 200 and the comment was gone. The delete gate asks the sharing service's canEdit(parent), the call objectstack-ai/objectstack#22455 reports for attachments, and it answers true for every controlled_by_parent parent. 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 (which service_manager spreads), system_admin (which tenant_admin spreads)) now also grant sys_comment read + create + edit and sys_activity read. guest_portal names neither. The enable.feeds paragraph of the canonical note in src/sales/objects/index.ts is 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 403 PERMISSION_DENIED on all six parents. In Chromium as na.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_ENABLED in the environment) logs flow 'quote_expiration' declares a 'schedule' trigger but is NOT bound … OS_AUTOMATION_SCHEDULED_WORK_ENABLED is unset or not truthy, and the same for contract_expiration, contract_renewal and 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 id scheduled-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_template serves 76 crm.* rows: 19 names in en-US, es-ES, ja-JP and zh-CN. All 19 notify nodes under src/*/flows name 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' printed os-verify-lock: VERDICT command-exit 0. The steps reported:
    • Validation passed
    • i18n lint gate 0 issues
    • source hygiene clean
    • source token ratchet clean (src/sales authored total ~101,244 of ~107,000)
    • Build complete
    • Test Files 174 passed (174), Tests 3616 passed | 1 skipped (3617)
  • New test/record-comments-access.test.ts runs on the shared boot, which carries AuditPlugin. A comment and an activity row sit on a quote the rep reaches through north_america_territory and on one they do not reach. The test pins five things:
    • the rep lists only the first quote's comments;
    • the rep reads only the first quote's activity;
    • a post gets 201 on the first quote and 403 RECORD_NOT_ACCESSIBLE on the second;
    • deleting another person's comment answers 403 PERMISSION_DENIED;
    • controls: the rep reads one quote and not the other, and both quotes carry rows.
  • test/collaboration-capabilities.test.ts pins the grant shape on every set that reads a record, and that guest_portal names neither object.
  • Ablations, on committed head ff6b059, through objectstack scripts/ablation-replace.mjs. Each anchor went from 1 hit to 0, the blob changed, and the restore was verified as blob == HEAD with an empty git diff HEAD:
    • Deleting sales_rep's sys_comment line turned 3 tests red with 403 PERMISSION_DENIED: the list, the post, and the structural test naming sales_rep → sys_comment (3 failed | 12 passed).
    • Deleting sales_rep's sys_activity line turned the activity case red with 403 (1 failed | 5 passed).
    • Both turned red, which is the expected direction.
  • Docs suites (test/docs-*.test.ts, automation coverage, sharing coverage, state machines) pass: 29 files, 647 tests, the anchor-link audit included.

Acceptance notes

Patch round 1

Head 0a2afd0. It merges main at 4fbfde1, 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 in src/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's docs[]). Each sweep these guides name now carries the scheduled-work condition and says what a default install does instead. Every time is unchanged, so test/docs-drift.test.ts still 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, but GET /api/v1/meta/doc/crm_sales is 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_admin and crm_overview all 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 as na.rep on f3b5cf4 and again as admin on 0a2afd0.

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 verify printed os-verify-lock: VERDICT command-exit 0. The steps reported:

  • Validation passed
  • i18n lint gate 0 issues
  • source hygiene clean
  • token ratchet clean (src/sales authored total ~101,252 of ~107,000)
  • Build complete
  • Test Files 174 passed (174), Tests 3616 passed | 1 skipped (3617)

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, and GET /api/v1/meta/doc/crm_sales is the door the finding measured.


Generated by Claude Code

claude added 3 commits October 9, 2026 14:24
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>
@vercel

vercel Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Oct 9, 2026 3:36pm UTC

Request Review

claude added 2 commits October 9, 2026 15:15
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants