Stories · page 3 of 5
Salesforce Cases as the story source: the org, the filter, the mapping, what Foreman writes back.
A project that uses the Salesforce pack can take its stories from Salesforce Cases instead of
markdown files or Jira. Each Case that a filter finds in one sandbox is a story. This page is for
the admin who sets that up: the org, how a Case becomes a story, the filter, dependencies and
owner tasks, what Foreman writes back, the switches, what happens when the org does not answer,
and how to check it. The Salesforce pack is a preview: its release paths pass their own tests,
but no story has yet shipped through them on a live org, and the Gearset path has not run
against a live Gearset pipeline. Put this in foreman.config.json (Set up › This project writes
a starter version of it):
"pack": "salesforce",
"salesforce": {
"orgs": { "acme-qa": { "orgId": "00D000000000002AAA" } },
"build": "acme-qa",
"stages": [{ "name": "qa", "org": "acme-qa" }]
},
"stories": {
"adapter": "salesforce",
"org": "acme-qa",
"where": "IsClosed = false AND Type = 'Foreman'",
"orderBy": "CreatedDate ASC",
"doneWhenField": "Done_When__c",
"dependsOnField": "Depends_On__c",
"ownerField": "Type",
"ownerValue": "Owner task",
"statuses": { "started": "Working", "review": "Escalated", "blocked": "On Hold", "shipped": "Closed" }
}
Every option of the block:
| Name | What it does | Default |
|---|---|---|
org |
The alias in salesforce.orgs of the sandbox that holds the Cases. Required. |
none |
where |
A SOQL condition on Case, at most 4,000 characters. Give this or listView. |
none |
listView |
The API name of a Case list view. Give this or where. |
none |
orderBy |
With where only: field names with ASC or DESC, the pick order. |
CreatedDate ASC |
pollSeconds |
How long the last list is reused before the org is asked again; 0 asks every time. |
60 |
idPattern |
What a Case number looks like. | \d{8,10} |
doneWhenField |
A Case field holding “Done when”. | unset: the heading in the Description |
dependsOnField |
A Case field holding the numbers of the Cases this one waits for. | unset |
ownerField |
A picklist or multi-select field that marks owner tasks. Needs ownerValue. |
unset |
ownerValue |
The value of ownerField that means an owner task. |
unset |
statuses |
{ event: Case Status } for started, review, blocked, failed and shipped. Each name at most 255 characters, with no quote, backslash or line break. |
{}: comments only |
briefsDir |
Where the briefs are written, relative to the project. | .foreman/stories |
The org
The Cases come from stories.org, the alias of an org in salesforce.orgs (the pack is
required: "pack": "salesforce"). It must be a sandbox, never a production org. Foreman
refuses a production org in three places: foreman doctor and the config check refuse an org
marked "production": true or named prod or production; the story source refuses to open on
one; and every poll asks the org itself whether it is a sandbox, with the org ID
salesforce.orgs names, before it reads a Case. An org that answers “not a sandbox” or
“another org ID” is an error, never served from the cache. The org can be one a stage or the
build already uses, or a sandbox that only holds the Cases. Foreman reaches it through its own
sign-in (Set up › Salesforce), in an sf home of its own, never the sf logins you use
yourself. Each sf call is given 60 seconds.
The mapping
The Case number is the story id (00001026), the Subject is the title and the Description is
the brief. A “Done when” heading in the Description, at any level, or the Case field named in
doneWhenField, says when it is finished; a field with HTML is turned into plain text.
idPattern says what a Case number looks like (default \d{8,10}); change it when your org
numbers Cases another way. The Case’s position in the query’s answer is its order; groups are
not used, and the story’s link is the Case in Lightning.
A story is done when the Case is closed. Otherwise its status is the one Foreman last recorded
(in-progress, blocked, ready-to-promote), else todo. What Foreman records lives in a
state overlay, .foreman/stories/<number>.json, never in the Case.
Before a story starts, Foreman reads the Case with its last 10 comments and writes a brief to
.foreman/stories/<number>.md (briefsDir moves it): the frontmatter (id, title,
status, url), ## What, ## Done when, ## Comments newest first, and ## Progress notes
from the overlay. The agents read that file.
There is no way to create a Case from Foreman: a Case is made in Salesforce. So foreman learn
proposes no follow-up stories for this source (“follow-ups not proposed: this story source
cannot create stories”), and features.autoCreateFollowUps has nothing to create.
The filter
Give exactly one of where or listView. where is a SOQL condition on Case, and orderBy
(default CreatedDate ASC) sets the order they are picked in; Foreman runs
SELECT … FROM Case WHERE (<where>) ORDER BY <orderBy> LIMIT 200. listView is the API name
of a Case list view; Foreman reads the view’s own query from the org and uses its filter, its
order and its USING SCOPE, with its limit replaced by 200. orderBy is refused with a list
view, which has its own order. Either way Foreman reads at most 200 Cases at a time, and
pollSeconds (default 60) is how long it reuses the list before it asks again.
Dependencies and owner tasks
dependsOnField names a Case field, such as a text or long text field, that holds the numbers
of the Cases a Case waits for, separated by commas, spaces or semicolons. A number that does not
match idPattern is ignored. A Case outside the filter is looked up in one more query: one that
is closed counts as done; one that is open holds the story, and foreman story plan names it
as “outside the filter, not done” (or “not found” when there is no such Case). Once Foreman has
written a depends_on for a story, its list is the one that counts.
An owner task is yours to do and is never picked. Mark one with ownerField and ownerValue
(a picklist or multi-select field and the value that means an owner task; in a multi-select,
any one of the chosen values matches), or, with neither set, put “(owner task)” in the
Subject. ownerValue is required with ownerField, and refused without it. The card the
dashboard shows for an owner task reads its headings from the Case’s Description; see
Owner tasks.
What Foreman writes back
Every comment starts with “Foreman:” and is private to your team. Foreman posts it with
IsPublished off, so a customer never sees it.
| When | Comment | Status |
|---|---|---|
| A story starts | “Started.” | started |
| Its pull request is open | “Pull request ready for review: <link> (commit <short sha>)” | review |
| It is blocked | “Blocked: <reason>” | blocked |
| It fails | “Failed: <reason>” | failed |
| It ships | the release note | shipped |
The statuses map names the Case Status to set for each event. Leave an event out to only
comment for it. A name must be an active value of the Case Status picklist; one the org
refuses is dropped, with a warning in the events, and the story carries on. The same event with
the same note twice in a row writes nothing new. A depends_on Foreman adds or takes off a
story is written to dependsOnField, whatever the switches say.
With salesforceComments on, the story also holds the reasons it took the path it did (the
questions Foreman asked you and your answers, the architect’s design, each round of review,
how a session stopped, a release that stopped), each as its own private comment of at most
4,000 characters; a longer one is split into numbered parts, “Foreman: <title> (part i of n)”.
features.jiraCommentDetail chooses whether a round’s findings are counted or listed, for
this source too. The notes are described in full on
Stories from Jira;
foreman story note <id> --kind design --file <plan> records a design by hand here as well.
The two switches
foreman features off salesforceComments stops the comments and the story notes, and
foreman features off salesforceStatuses stops the Status changes. Both are on by default and
apply from the next story. They are listed on
Settings and switches and appear only while
stories.adapter is salesforce.
When Salesforce does not answer
The supervisor never waits on sf. A refresher of its own polls the filter every
pollSeconds (at least every 5 seconds) in a child process that is given 3 minutes, and writes
the answer to .foreman/salesforce/list.json. Commands and the supervisor share that cache, so
one poll serves everyone. The answer is reused only while the org, the filter, the order and
the fields named are the same.
A write the org does not take waits in .foreman/salesforce-outbox/, one small file per write,
and is sent again, in order, before the next write or after the next poll that reaches the org.
Before any write, Foreman makes the same sandbox check a poll makes (once per pollSeconds);
when it fails the writes stay queued, with a warning, and nothing is sent. Each queue, replay
and drop is an event in .foreman/events.jsonl, prefixed “Salesforce:”.
While the org cannot be reached, the last answer of any age stands in, so a story under way can
still record its progress, and Foreman picks no new story: the run shows “Salesforce not
reachable since HH:MM: <why>; picking paused, stories in progress carry on” while the last
refresh failed or the cache is older than 5 × pollSeconds. Stories already under way carry
on. If the sign-in to the org lapses, Foreman sends the paused notification: “Foreman’s sign-in
to the Salesforce org that holds the stories has lapsed: connect it again on Set up ›
Salesforce, then resume.”
Checking it
foreman doctor adds three checks to the Salesforce group:
| Check | What it says |
|---|---|
salesforce stories options |
The options are valid, the org is in salesforce.orgs and is not production: “Cases in acme-qa”. |
salesforce stories filter |
The filter runs in the org and finds a number of Cases (“finds 12 Cases”; past 200, “Foreman reads the first 200”). Skipped when the options fail or the org did not pass its own check. |
salesforce stories statuses |
Each statuses value is an active Case Status, with a near match named (“did you mean “Working”?“), and each field you named (doneWhenField, dependsOnField, ownerField) exists on Case. “none set” when there is nothing to check. |
Set up › This project has a Story source choice, Salesforce Cases, for a project that already
has the pack on: Foreman saves a starter filter of the open Cases of Type Foreman, in the first
sandbox of salesforce.orgs, and you edit the filter in foreman.config.json. The pack cannot
be turned off while the stories come from Cases: “The stories come from Salesforce Cases: pick
another story source before turning the pack off.” The rest of the pack’s setup is on
Salesforce set-up and the config block on
salesforce config.
Was this page useful? Something missing or wrong?Email us and say which section.