Skip to content

marketing/index still promises "Automatic enrollment" — the fifth false-automation claim about the same screen flow #1859

Closed

Description

@os-steve

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

  1. added
    documentationImprovements or additions to documentation
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 10, 2026
  2. self-assigned this
    on Sep 10, 2026
  3. os-steve commented on Sep 10, 2026

    @os-steve
    CollaboratorAuthor

    Claim: session_01DuzfS5chho38Yx1jxx9DEj → branch claude/issue-1859-marketing-index-automation-claims

    Dispatched by the hotcrm PM seat (#1353). The dev inherits this claim — ⛔ no second Claim:, ⛔ never touch the assignee.

    Base off main fetched 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 verify green. ⛔ 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:

    :27  2. **In Progress** — launch the campaign; the system bulk-enrolls the target audience…
    :28  3. **Track engagement** — members move through *Sent → Opened → Clicked → Responded → Converted*.   ← #1850 rewrote THIS
    :34  - **Automatic enrollment** — when a campaign moves to *In Progress*, a scheduled flow bulk-enrolls…
    :35  - **Automatic status updates** — opens, clicks, bounces, unsubscribes update the member status…      ← #1850 rewrote THIS
    

    ⭐⭐ 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_enrollment is type: 'screen' (campaign-enrollment.flow.ts:52), entered only through the enroll_leads action, label Enroll Members; its docblock records the Monday 9 AM cron was removed because a cron firing seeds no inputs.

    ⚠️ The third bullet is NOT part of this card's premise — measure it

    :36 claims "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:

    • it is real ⇒ leave it, and say in the report which flow and schedule you matched it to;
    • it is not ⇒ it is the same defect and belongs in this PR.

    ⛔ Do not leave it unexamined because the card did not name it.

    The structural half

    :20 draws the life cycle as Planning ──► 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

    Proof obligation

    ⚠️ Siblings live on content/docs/analytics/** (#1853) and src/actions/opportunity.actions.ts (#1857). Neither meets your files.


    Generated by Claude Code

  4. os-steve commented on Sep 10, 2026

    @os-steve
    CollaboratorAuthor

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationi18npm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions