Repository navigation
marketing/index still promises "Automatic enrollment" — the fifth false-automation claim about the same screen flow #1859
Description
Found while fixing #1854, which rewrote item 1 of "How members get enrolled" on campaign-members.mdx and the matching paragraphs on campaigns.mdx. This is the third marketing page — content/docs/marketing/index.mdx and both siblings — and it is out of that card's six-file scope, so it is filed rather than quietly rewritten (the discipline #1846 used when it filed #1845, and #1855 when it filed #1854).
Measured against origin/main at 7c09fb8.
The finding
campaign_enrollment is a screen flow with no trigger of its own — src/flows/campaign-enrollment.flow.ts:52 declares type: 'screen', no start node carries a schedule, and its only entry point is enroll_leads (label Enroll Members, type: 'flow' on crm_campaign, src/actions/campaign.actions.ts:22-35). #1854 established this and its PR corrected two pages. The overview page was not in that card's scope and still carries the same two claims:
| page | line | claim |
|---|---|---|
content/docs/marketing/index.mdx |
:34 |
Automatic enrollment — "when a campaign moves to In Progress, a scheduled flow bulk-enrolls members matching your target audience" — under the heading What the system does for you |
content/docs/marketing/index.mdx |
:27 |
"In Progress — launch the campaign; the system bulk-enrolls the target audience as campaign members" |
index.zh-Hans.mdx |
:27, :34 |
系统将目标受众批量加入为营销活动成员 / 自动加入 — …定时流程会批量加入匹配你目标受众的成员 |
index.zh-Hant.mdx |
:27, :34 |
系統將目標受眾批次加入為行銷活動成員 / 自動加入 — …定時流程會批次加入符合你目標受眾的成員 |
Both triggers are false: nothing fires the flow on a status change and nothing schedules it. :34 is the stronger one — it sits in the list of things the app does by itself, and its bullet title is literally Automatic enrollment.
The ASCII life-cycle diagram at :20-24 draws the same claim as an arrow (In Progress ──► Enroll leads/contacts), which is why this is not a one-line fix and did not belong inside #1854's diff.
Why it survived the earlier sweeps
This page has been edited twice this shift and neither pass was looking for this. .changeset/analytics-pages-stop-stamping-opens-and-clicks.md and .changeset/campaigns-page-drops-the-rates-nothing-computes.md rewrote the Marketing Cloud overview's life-cycle step 3 and the engagement bullet at :35 — the bullet directly under this one — for the opens/clicks vocabulary. The enrollment bullet above it was left as it stood.
What a fix looks like
Three files. Move enrollment out of "What the system does for you" (it is not something the system does for you), name Enroll Members as the gesture, and decide what the life-cycle diagram should draw now that the arrow out of In Progress is a person, not a trigger. marketing/campaigns#campaign-enrollment-flow now carries the full description, so the overview can point at it rather than restate it.
⛔ Do not describe the removed cron as if it might return, and ⛔ do not propose adding one — the criteria are answered on the flow's screen and a scheduled run has nobody to answer them (campaign-enrollment.flow.ts:13-19).
zh-Hans takes the zh-CN pack's shipped label for enroll_leads (批量加入成员, src/translations/zh-CN/objects.marketing.ts:90-93); zh-Hant mirrors it in that page's own Traditional conventions (批次加入成員), never a mechanical conversion.
Refs: #1854 (the same claim on the two campaign pages) · #1845 / #1846 / #1850 / #1852 (the earlier passes over this family) · #597 (the card that made the flow a screen flow).
Activity
os-steve commentedon Sep 10, 2026
Claim:session_01DuzfS5chho38Yx1jxx9DEj→ branchclaude/issue-1859-marketing-index-automation-claimsDispatched by the hotcrm PM seat (#1353). The dev inherits this claim — ⛔ no second
Claim:, ⛔ never touch the assignee.Base off
mainfetched fresh — PR #1850 and PR #1860 have both edited this page family; base after #1860 lands.Model: Opus. Deliverable: one PR, ready for review,
pnpm verifygreen. ⛔ Do not arm auto-merge, ⛔ do not merge.PM verification — and ⭐ the diagnosis is confirmed by the layout itself
The two surviving claims, and the reason they survived, are visible in one screenful:
⭐⭐ Both survivors sit directly above a line a previous pass rewrote. The card's diagnosis — that it survived two sweeps because both were fixing the bullet immediately below — is not a theory; it is the file's own geometry. A sweep's blind spot is the line adjacent to the one it came for. ⇒ read this page top to bottom; ⛔ do not trust that the neighbours of your targets are clean.
Ground truth (established, ⛔ do not re-derive):
campaign_enrollmentistype: 'screen'(campaign-enrollment.flow.ts:52), entered only through theenroll_leadsaction, label Enroll Members; its docblock records the Monday 9 AM cron was removed because a cron firing seeds no inputs.:36claims "Automatic completion — campaigns whose end date passes are auto-marked Completed at 2 AM daily." Nobody has checked it. It is the third bullet in the same list under the same heading, so it is either a real scheduled flow or a third false-automation claim.⭐ Measure it against
src/flows/before you write anything about it. Both outcomes are fine and both must be reported:⛔ Do not leave it unexamined because the card did not name it.
The structural half
:20draws the life cycle asPlanning ──► In Progress ──► Completed, with the enrollment arrow implied out of In Progress. So this is a page-structure fix, not four line edits: the diagram, the numbered life cycle and the "What the system does for you" list all have to end up telling the same story.⭐ The honest shape is available and is better copy than what is there: the campaign's status moves on its own where it really does; enrollment is an act a marketer performs, from the campaign, via Enroll Members. A heading called "What the system does for you" that lists a thing a person does is the defect in one line.
Rulings
enroll_leadsis 批量加入成员 (translations/zh-CN/objects.marketing.ts:90-93); zh-Hant pages use 批次加入成員. ⭐ Look labels up; the literal translation was not the shipped one here.test/**(epic Epic: bring this repo's test farm back under the 2026-08-31 ruling — platform-first,os lint --strictfirst, then retire the local re-implementations by family #1579's fence). ⛔ Nocontent/docs/releases/edit.Proof obligation
node:fsto a facade recording every read, attributed per test file) — PR docs(marketing): campaign enrollment is a button, not a trigger or a cron #1860 reused it and got 15 guards. ⛔ Keep it a probe; it breaks 7 unrelated fs-tooling suites.pnpm verifystage by stage, every exit code captured before any pipe. Test-file / test counts before and after.content/docs/analytics/**(#1853) andsrc/actions/opportunity.actions.ts(#1857). Neither meets your files.Generated by Claude Code
os-steve commentedon Sep 10, 2026
os-dev-report
{
"issue": 1859,
"status": "done",
"branch": "claude/issue-1859-marketing-index-automation-claims",
"pr": "#1865",
"premise_still_valid": true,
"summary": "The card's premise held on origin/main at 2231583 (PR #1860's merge commit): campaign_enrollment is type: 'screen' (campaign-enrollment.flow.ts:52), no start node carries a schedule, and enroll_leads (label Enroll Members) is its only entry point — so all three enrollment claims on content/docs/marketing/index.mdx and both siblings were false. Fixed as a page-structure change rather than four line edits: the ASCII diagram now separates a STATUS track (whose last hop really is the nightly sweep) from a MEMBERS track that names Enroll Members as a button, the numbered life cycle gains enrollment as its own step placed where the action is actually offered (Planning OR In Progress), step 3 says moving the status is the launch and enrolls nobody, and the 'What the system does for you' list closes with the sentence that stops it coming back, carrying the flow docblock's reason (a cron firing seeds no inputs). MEASURED, as instructed: the unnamed third bullet, 'Automatic completion', is REAL — campaign_completion (src/flows/campaign-completion.flow.ts:24-28) is type: 'schedule', status: 'active', registered at src/flows/index.ts:104, schedule '0 2 * * ' at :36, filter { status: 'in_progress', end_date: { $lt: '{TODAY()}' } } at :41, writing status: 'completed' at :55; it is kept. DECLARED DEVIATION from 'real ⇒ leave it': its title and substance are untouched, but its wording gained the status qualifier its own filter carries ('an In Progress campaign whose end date has passed'), because the life-cycle step beside it and the Campaigns page since #1860 both state that qualifier and the dispatch required the page to tell one story — flagging it rather than doing it silently. The whole page was read top to bottom, not only at the named lines: the other five automation claims (:35 conversion tracking, :37 live metrics, :38 budget warning, :39 ROI rollup, :58 attribution) and the two counts (:43 8 types, :54 5 channels) were each measured against src/ and are all true, so they are kept verbatim. Second independent proof that no schedule enrolls anybody: nine type: 'schedule' flows ship, campaign_completion is the only one that writes crm_campaign (demo_bootstrap only backfills owner_id), and no scheduled flow names crm_campaign_member at all — grep -rln 'crm_campaign_member' src/flows/ returns only campaign-enrollment.flow.ts and the campaign-member-enroll.flow.ts subflows it calls. zh-Hans takes the zh-CN pack's shipped label 批量加入成员 (translations/zh-CN/objects.marketing.ts:90-93); zh-Hant mirrors it as 批次加入成員 with 排程 / 畫面 / 區隔, the spellings those pages already carry. Both Chinese pages stay link-free into #campaign-enrollment-flow and name the section in text (PR #1860's precedent), because the Chinese target headings carry no explicit [#id] and AGENTS.md doc rule 6 / #1359 exists to stop links depending on a translated slug. No metadata change, nothing under test/**, no content/docs/releases/ edit. PR #1865 is open and READY FOR REVIEW (draft: false); auto-merge NOT armed, nothing merged.",
"tests": "pnpm verify run stage by stage on the committed tree at 23d403f, every exit code captured BEFORE any pipe (stage redirected to a file, then EXIT=$?); the heavy stages went through this container's shared verify lock, each hold ending 'VERDICT command-exit 0'. validate=0 (pre-existing advisories only) · typecheck=0 (tsc --noEmit, no output) · lint=0 ('4 warning(s), 12 suggestion(s)', all pre-existing) · lint:i18n-gate=0 ('i18n lint gate: 0 i18n/missing- issues') · hygiene=0 ('source hygiene clean') · hygiene:tokens=0 ('source token ratchet clean'; business semantics ~85,285 · interaction layer ~38,585 · authored total ~138,515) · build=0 ('Artifact: dist/objectstack.json (1918.4 KB)') · test=0 ('Test Files 165 passed (165) / Tests 3455 passed | 1 skipped (3456)'). BEFORE counts on the same worktree at 2231583: 165 files, 3455 passed / 1 skipped (3456) — identical; no test file added or removed and nothing under test/** touched. READ-SET MEASURED with PR #1855's instrument (scratchpad-only vitest config aliasing node:fs and node:fs/promises to a recording facade, attributed per test file via the call stack): 14 guards read a page body — docs-anchor-links (via its only consumer test/helpers/docs-anchors.ts), docs-locale-callouts, docs-metadata-counts, docs-setup-navigation-names, docs-runnable-samples, docs-drift, docs-analytics-vocabulary, docs-dashboard-tiles, docs-retired-personas, docs-role-hierarchy, docs-zh-hant-justification (all three files); docs-conversion-rate-spelling and docs-object-term-consistency (zh only); docs-view-rosters (English only). The probe broke exactly the 7 unrelated fs-tooling suites #1855 predicted (7 failed / 158 passed under it, against 165/165 without) and was committed nowhere. docs-object-coverage is deliberately NOT in the list: unlike #1860's read-set it never opens marketing/index.mdx (its ledger keys crm_campaign to marketing/campaigns), and the probe recorded zero reads from it — a real difference from the sibling PR, not an omission. check-source-hygiene.mjs reads the pages outside vitest (content/ and .changeset/ are in TEXT_SCANNED, scripts/lib/source-hygiene-surface.mjs:123) and is green. PARITY MEASURED FIRST (working tree against HEAD: blobs, all three pages): callouts 0/0/0, headings 9/9/9, bold-arrow 0/0/0, table rows 4/4/4, code fences 1/1/1 — identical before and after; ONE quantity moves, anchored links on index.mdx 0 -> 2 (zh 0 -> 0). ABLATION ANYWAY, on the committed tree, three legs, each mutating then proving the mutation reached disk by counting the exact token, running the guard with its exit code captured before any pipe, restoring with 'git checkout HEAD -- (absolute path)' and proving the restore by blob hash against the HEAD blob plus an empty git diff HEAD (script carries trap ... EXIT INT TERM with absolute paths): leg A repointed the step-2 link this PR wrote at #campaign-enrollment-cron (injected token 1, remaining #campaign-enrollment-flow 2 -> 1) and docs-anchor-links exited 1 printing 'content/docs/marketing/index.mdx:28 /docs/marketing/campaigns#campaign-enrollment-cron — marketing/campaigns.mdx has no heading with id "campaign-enrollment-cron"' — load-bearing, it names a line this PR wrote; leg B added one blockquote to index.mdx only (^> lines 0 -> 1) and docs-locale-callouts exited 1 printing 'content/docs/marketing/index.zh-Hans.mdx: 0 callout(s), but content/docs/marketing/index.mdx has 1' — the inverse of #1860's leg, since these pages carry zero callouts the mutation had to ADD one, which is the direction that proves the pair is not comparing zeros; leg C inserted 'The app ships 99 flows.' and docs-metadata-counts exited 1 printing 'content/docs/marketing/index.mdx:57 says "99 flows", the stack registers 29 flows'. All three restored to blob a403103 == HEAD blob with git diff HEAD empty. Control bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the three pages and the changeset returns nothing.",
"mcp_calls": "2 — mcp__github__create_pull_request and mcp__github__add_issue_comment. Every read went through the zero-quota channel: unauthenticated repo-scoped REST on this public repo, probed first (GET /repos/objectstack-ai/hotcrm -> 200), covering issue #1859 and its comments, PR #1860 and its PM review, PR #1855's instrument section, and the #1865 body read-back.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code