Admin legacy automation
Overview
This skill is the admin persona's companion to v1.3's coverage
extension of sfi.get_impact. v1.3 does not introduce a new
headline tool — it makes the existing impact graph aware of nine
new ComponentTypes (WorkflowRule, ApprovalProcess,
AssignmentRule, AutoResponseRule, EscalationRule,
DuplicateRule, MatchingRule, EmailTemplate, Letterhead)
and one new EdgeType (sendsEmail). The admin's first-line
question for these types is "what does this rule do?", "what
emails fire when X happens?", or "is this still active in our
org?" — and the honest answer is to walk the edges the extractors
emit from XML and present them as a structured rule → action →
target timeline.
The graph consumes one new EdgeType and reuses three existing
ones. The new edge is sendsEmail (a rule declares it will send
the named EmailTemplate; direction: {WorkflowRule | ApprovalProcess | AutoResponseRule} → EmailTemplate,
confidence: declared). The reused edges, all declared in
v1.3's emission set, are triggersOn (rule → parent
CustomObject), references (rule → action target or approver
or assignee or matching rule), and parentOf (one-file-many-
rules container relationship from CustomObject to each per-
rule node). The boundary that matters for admins: v1.3 lists
what's configured, it does not simulate runtime. Workflow rule
criteria, time-dependent actions, approval routing per record,
and duplicate fuzzy-match decisions all need record-level data
v1.x doesn't have. The skill surfaces this gap explicitly rather
than fabricating an evaluated answer.
When to fire
Fire this skill on legacy-automation phrasing. Concrete triggers:
- "What workflow rules touch / fire on object Y?" — "what
workflow rules touch Account?", "which workflows fire on a Case
update?", "show me the workflows on Opportunity.".
- "Which approval processes are still active?" — "which
approval processes are active on Opportunity?", "list the
approval processes for Account.", "are the discount approvals
still in use?".
- "What assignment rule routes Cases / Leads?" — "what's the
assignment rule for Lead?", "how does Case routing work?", "show
me the lead-routing rules.".
- "Is this workflow being replaced by a flow?" — "should I
migrate
Notify_Sales_On_New_Tier1 to Flow?", "is this
workflow legacy?", "what do I lose if I move this rule to
Flow?".
- "Which fields does this duplicate rule check?" — "what
fields does
DuplicateRule:Account.Standard_Duplicate compare?",
"show me the matching rule behind the lead duplicate check.".
- "What emails go out for this approval / rule?" — "which
email template fires when the discount approval is submitted?",
"what auto-response goes out for a web lead?", "which approvals
send the
Sales/WelcomeEmail template?".
- "Show me the auto-response rules for Lead / Case." — "what
auto-responses do we have on Lead?", "list the autoresponse
rules.".
- "What's the escalation chain on Case?" — "show me the
escalation rules for Case.", "what queue does P1 escalate to?",
"when does a P1 case escalate?".
- "Which rules use the
{TemplateName} email template?" —
inversion question; walks the sendsEmail edge in the in
direction.
- "What does this letterhead get used by?" — leaf-node usage
query; walks the
references edge inward to EmailTemplate
nodes.
- "Why did this account get assigned to this queue?" — the
configuration answer is the AssignmentRule that names the
queue; the record-level answer needs live data and is out of
scope. Surface both halves.
- "What's the criteria for this rule?" — surface the extracted
criteria count from wherever that family puts it (node property on
WorkflowRule; per-entry EDGE property on AssignmentRule /
AutoResponseRule; absent entirely on EscalationRule) plus the rule's
formula where present. The evaluation of the criteria against a
record needs record-level data and is out of scope.
When NOT to fire
Defer to another skill when:
- The user asks "what breaks if I change X?" — cross-component
impact analysis. Defer to
architect-impact-analysis →
sfi.get_impact. (That skill already walks the v1.3 edges; it
just frames the answer for architects rather than admins.)
- The user asks "why can't user X see this record?" — visibility,
not automation. Defer to
admin-sharing-troubleshooting →
sfi.why_cant_user_see_record.
- The user asks "what layout does user X see?" — page-layout
routing. Defer to
admin-page-layout-routing →
sfi.layout_for_user.
- The user asks "where is this field used in Apex?" —
code-side reference question. Defer to
developer-apex-refactor
→ sfi.find_code_usages.
- The user asks "what does this flow do?" — Flow narration is
outside v1.3 (and outside the legacy-automation skill regardless;
Flow is the modern automation tier). Defer to
business-user-orientation's process-explanation intent, which
refuses to narrate Flow XML as English. If the user wants to know
whether a workflow rule has been replaced by a Flow on the same
object, this skill can help — list both and call out that the
workflow rule is still active per its <active>true</active>
property.
- The user asks for record-level evaluation ("does this
workflow rule actually fire on Account 001xx12345?", "is this
approval process triggered for this specific opportunity?").
v1.3 lists what's configured; it doesn't simulate execution.
Refuse honestly; direct the admin to Setup → Workflow Rules
or Setup → Approval Processes in the Salesforce UI, or to a
live
sf data query against their org.
- The user wants to refresh, init, or check vault status. Fire
refreshing-the-org-vault, /sfi-init, or pre-flight-checks.
- The user names an automation type outside v1.3's nine —
OutboundMessage, WorkflowTask, OrgWideEmailAddress, or
Process Builder as a standalone type (Process Builder is
metadata-shaped as a Flow, covered by v0.1's Flow extractor).
Say so plainly and name the boundary; offer the closest partial
if one exists (e.g., the rule's action references the outbound
message by name even though the OutboundMessage node itself isn't
extracted).
Steps
Walk these in order. Each step has a definite output that feeds
the next.
Step 1 — Parse the question into (object, automation-type, [rule-name])
The user almost never types a canonical id. They say "the
workflows on Account", "the discount approval", "the lead
auto-response". Translate to one or both of:
- A type-scoped enumeration when the user asks "which / what
/ show me" — translate "the workflow rules on Account" to
(type: 'WorkflowRule', parentId: 'CustomObject:Account').
- A specific rule id when the user names a rule — translate
"the Notify Sales workflow" to a search call, then to
WorkflowRule:Account.Notify_Sales_On_New_Tier1 (the canonical
form). The format per type is:
| Type |
Canonical id format |
Example |
WorkflowRule |
WorkflowRule:{ObjectApiName}.{RuleFullName} |
WorkflowRule:Account.Notify_Sales_On_New_Tier1 |
ApprovalProcess |
ApprovalProcess:{ObjectApiName}.{ProcessName} |
ApprovalProcess:Opportunity.Discount_Approval |
AssignmentRule |
AssignmentRule:{ObjectApiName}.{RuleFullName} |
AssignmentRule:Lead.Standard_Lead_Routing |
AutoResponseRule |
AutoResponseRule:{ObjectApiName}.{RuleFullName} |
AutoResponseRule:Lead.Standard_Web_To_Lead |
EscalationRule |
EscalationRule:{ObjectApiName}.{RuleFullName} |
EscalationRule:Case.P1_Case_Escalation |
DuplicateRule |
DuplicateRule:{ObjectApiName}.{RuleName} |
DuplicateRule:Account.Standard_Duplicate |
MatchingRule |
MatchingRule:{ObjectApiName}.{RuleFullName} |
MatchingRule:Account.Account_Name_And_Domain |
EmailTemplate |
EmailTemplate:{Folder}.{TemplateName} |
EmailTemplate:Sales.WelcomeEmail (note: folder, not object) |
Letterhead |
Letterhead:{LetterheadName} |
Letterhead:Corporate |
EmailTemplate is the outlier — its canonical id keys off the
template's folder, not an object (templates live under
email/{Folder}/{Template}.email-meta.xml). Letterhead is a leaf
node with zero outgoing edges (it has no references to anything;
only EmailTemplates reference it).
If the user names a rule by display label rather than API name
("the discount workflow"), call sfi.search_components({ query: 'discount workflow', types: ['WorkflowRule'] }) to translate
before firing the impact tool. Don't guess — workflow rule names
are bespoke per org. A canonical id that matches nothing at all now
returns error.kind: 'component-not-found' rather than an empty
slice, but an id that resolves to the WRONG component still answers
confidently about that component, and nothing in the payload will say
so.
If the user supplies a record-level shape ("does the discount
approval fire on opportunity 006xx12345?"), refuse the record-
level half and translate to the configuration question
explicitly: "I can tell you what ApprovalProcess:Opportunity. Discount_Approval is configured to do — entry criteria, approver
chain, email templates. v1.x can't tell you whether that
specific record would trigger it; check the record's
Approval History related list in Salesforce for that."
Step 2 — Enumerate or fetch the target node(s)
For type-scoped enumerations, call sfi.list_components:
{ "type": "WorkflowRule", "parentId": "CustomObject:Account" }
This returns every WorkflowRule:Account.* node sorted by id.
Surface the list with each rule's active property (read from
properties.isActive or properties.active on the node) so the
admin can immediately see which rules are live and which are
inactive but still extracted.
For specific rule queries, call sfi.get_component with the
canonical id to fetch the node's full properties (criteria
formula, action list, business-hours reference, etc.) before
walking edges. This gives the timeline its context.
Step 3 — Walk the impact / edge graph
For a rule-impact question ("what does this rule touch?"),
call sfi.get_impact with the rule's id:
{ "componentId": "WorkflowRule:Account.Notify_Sales_On_New_Tier1", "hops": 2 }
The response carries impact.nodes (the rule plus everything it
touches) and impact.edges. Walk the edges and bucket by
edgeType:
triggersOn — the parent object the rule fires on.
references — the rule's action targets (named
WorkflowAlert / FieldUpdate / OutboundMessage / Task, approver
Role / Group / User, assignee Queue / User, matching-rule
pointer from DuplicateRule).
sendsEmail — the EmailTemplate the rule's alert /
notification step sends.
callsApex — when a WorkflowRule action invokes an Apex
class via a <flowTrigger> / <workflowAction> of type
Apex, the edge points at the named ApexClass.
parentOf — the container relationship from
CustomObject:{Object} to each per-rule node; mostly
scaffolding, but surface it once for the record so the admin
knows the parent is CustomObject:Account (or whichever).
For an inversion question ("which rules send this email
template?"), use sfi.get_edges:
{
"nodeId": "EmailTemplate:Sales.WelcomeEmail",
"direction": "in",
"edgeType": "sendsEmail"
}
The response lists every WorkflowRule, ApprovalProcess, or
AutoResponseRule node with an outgoing sendsEmail edge into
the named template. Sort by rule type for legibility.
For an approver chain question ("who approves the discount
process?"), use sfi.get_edges:
{
"fromId": "ApprovalProcess:Opportunity.Discount_Approval",
"direction": "out",
"edgeType": "references"
}
The response lists every references edge to a Role, Group,
User, or Queue named in <approvalSteps>[*].<assignedApprover>.
Walk the list in step order (the extractor preserves it) and
present each step with its label and approver.
Step 4 — Follow sendsEmail to the EmailTemplate body
When a rule has a sendsEmail edge, the natural follow-up
question is "what does that email actually say?" Call
sfi.get_component on the EmailTemplate id and surface:
properties.subject — the email's subject line, verbatim.
properties.templateType — text, html, visualforce, or
custom. (The XML element is <type>; the extracted property is
templateType. There is no properties.type.)
- There is no
properties.body and no properties.content. The
extractor does not store the body string at all — it stores
properties.bodyLength, a character count (always 0 for
visualforce templates, whose body lives in the referenced VF page).
If the admin needs the body text, point them at the source file, or
read it via the sourcePath on the node.
- Merge tokens ARE tokenized. The v3.0 body-merge scan parses the
<content> body for {!Object.Field} and conditional
{!IF(... Object.Field ...)} merges. Each distinct field yields a
references edge with confidence: 'parsed', properties.role: 'body-merge', properties.mergeContext (the verbatim {!...}
token) and properties.conditional. The node mirrors this as
properties.mergeFields (canonical CustomField: ids) and
properties.referencedObjects. Do NOT tell the admin merge tokens
are un-tokenized — surface the resolved fields.
properties.richTemplateSyntaxDetected — true when at least one
merge appeared inside a function call. When true, disclose the real
boundary: the field REFERENCES are captured; the FIRING LOGIC of the
conditional is not.
properties.letterheadName — the named Letterhead, if any. (The XML
element is <letterhead>; the extracted property is
letterheadName. There is no properties.letterhead.)
If the template's properties.letterheadName is non-null, the
EmailTemplate node has a references edge to the named
Letterhead. Surface the letterhead's name for context; don't walk
further (Letterhead is a leaf — top/bottom/header colors and
styling, nothing more).
Step 5 — Present as a structured timeline
The raw response is a flat list of nodes and edges. The human
reading needs a grouped timeline: rule → trigger → criteria →
actions → targets. Walk the rule's edges and present in this
order:
- What it is. Rule type, canonical id, parent object,
active property. Surface inactive rules explicitly — they're
still extracted, but the admin needs to know "this rule is in
the metadata but active: false."
- When it fires. Trigger type for WorkflowRule
(
onCreateOnly, onAllChanges, etc.), entry criteria for
ApprovalProcess (formula text or criteriaItems), action
timing for EscalationRule — read minutesToEscalation off the
rule's outgoing references / sendsEmail EDGES, which is where
the extractor puts it; it is NOT a node property. Do NOT report
escalationStartTime: the extractor validates it (against the
real Salesforce enum, CaseCreation / CaseLastModified — no
other value exists) but does not store it, so the vault cannot
tell you which one a rule uses. Say so rather than guessing.
actionOnInsert / actionOnUpdate for DuplicateRule.
- Criteria summary. Surface the criteria count from the RIGHT
place for the family (WorkflowRule:
properties.criteriaItemCount;
AssignmentRule / AutoResponseRule: the per-ruleEntry
criteriaItemCount on the outgoing references / sendsEmail
edge; EscalationRule: unavailable — say so) plus
properties.formula where it exists. There is no
properties.criteriaItems node property on any of them. Do not
evaluate the criteria — that's record-level. The admin reads the
predicate themselves.
- Actions. Bucket by action category (Alert / FieldUpdate /
Apex / OutboundMessage / Task for WorkflowRule;
initialSubmission / finalApproval / finalRejection /
recallActions for ApprovalProcess; escalationAction[] for
EscalationRule; ruleEntry[] for AssignmentRule /
AutoResponseRule). For each action, name the target via its
references or sendsEmail edge.
- Targets. For each action target, cite the canonical id
(
EmailTemplate:Sales.WelcomeEmail, Queue:Tier2_Support,
MatchingRule:Account.Account_Name_And_Domain, etc.) and the
confidence (always declared for v1.3 emission).
- Disclosure. "v1.3 lists what's configured; doesn't
simulate runtime."
Do not silently drop steps. An inactive rule, an empty action
list, a dangling action target — they're all signal. The admin's
trust hinges on every extracted edge appearing in the report.
Step 6 — Honest disclosure when an edge dangles
The v1.0 graph store tolerates dangling edges. When a
WorkflowRule action references a target the extractor cannot
resolve — e.g., the rule's <fieldUpdates>[*].<field> names
Region__c but CustomField:Account.Region__c is not in the
extracted set — the edge is emitted with the target id intact and
the consumer sees "target not in extracted set" when walking.
Surface this verbatim, never silently drop it:
The workflow rule's fieldUpdate references
CustomField:Account.Region__c, which is not in the
extracted set. Either the field has been removed from the org
since the last refresh, the field name was renamed in metadata
and the workflow points at the old name, or the extraction
missed it. Verify in Setup → Object Manager → Account →
Fields.
This is the v1.3 honesty anchor (Q35). The dangling edge is
evidence, not a footnote — it's how the admin learns that the
workflow rule is pointing at a field that no longer exists in the
expected place.
Step 7 — Append the v1.3 boundary disclosure
Every response from this skill ends with the fixed-form boundary
disclosure (next section). When the response surfaces a dangling
edge, restate it; otherwise it appears once at the end.
Step 8 — When the question is migration-shaped, add the Flow recommendation
If the admin asked "should I migrate this workflow to Flow?",
"is this workflow legacy?", or "what would I lose by moving this
to Flow?", walk the impact chain first so the migration
conversation has the full dependency picture. Then add the
migration paragraph:
Salesforce has superseded WorkflowRule and Process Builder
with Flow for most use cases. Workflow Rules are in the multi-
release retirement track (retirement began in 2024+); Process
Builder is already retired. v1.3 still extracts WorkflowRules
honestly because real orgs still ship them in production —
but when planning changes, consider whether the rule should
move to Flow before extending it. Migration considerations
grounded in the impact chain above:
- Every
references / sendsEmail / callsApex target listed
above must be reproduced in the new Flow.
- Time-dependent workflow actions (
<workflowTimeTriggers>)
require Scheduled Paths in the migrating Flow.
- The criteria predicate translates to a Flow start condition
or a Decision element.
- The Salesforce Migrate to Flow tool (Setup → Workflow
Rules → Migrate to Flow) handles most cases mechanically;
complex Time-Dependent Actions may require manual rewrite.
The migration disclosure is a footer, not a replacement for
the impact analysis. An admin planning a migration without the
full target list will produce an incomplete Flow.
Reporting format
Use the synthetic-v1.3 fixture for the worked example. Suppose
the admin asks:
"What workflow rules touch Account?"
Claude's flow:
- Parse →
(type: 'WorkflowRule', parentId: 'CustomObject:Account').
- Enumerate via
sfi.list_components (illustrative):
{
"data": {
"components": [
{
"id": "WorkflowRule:Account.Notify_Sales_On_New_Tier1",
"type": "WorkflowRule",
"properties": {
"active": true,
"triggerType": "onCreateOnly",
"criteriaItemCount": 1
}
},
{
"id": "WorkflowRule:Account.Backfill_Region_On_Edit",
"type": "WorkflowRule",
"properties": {
"active": false,
"triggerType": "onAllChanges",
"criteriaItemCount": 2
}
}
]
}
}
- Walk each rule's impact via
sfi.get_impact on each id.
- Respond:
Workflow rules on CustomObject:Account (extracted from
workflows/Account.workflow-meta.xml):
WorkflowRule:Account.Notify_Sales_On_New_Tier1 —
active: true, triggerType: onCreateOnly,
criteriaItemCount: 1.
- Triggers on
CustomObject:Account (triggersOn,
declared).
- Criteria (verbatim, not evaluated):
Account.Type equals 'Tier 1'. v1.3 lists what's configured; the
per-record evaluation needs record data.
- Actions:
- Alert action
Email_Sales_Team (references,
declared) → sendsEmail to
EmailTemplate:Sales.WelcomeEmail (declared).
- FieldUpdate action
Set_Region_From_Country
(references, declared) — sets
CustomField:Account.Region__c via the formula
IF(BillingCountry = "US", "Americas", "EMEA").
WorkflowRule:Account.Backfill_Region_On_Edit —
active: false, triggerType: onAllChanges,
criteriaItemCount: 2.
- Triggers on
CustomObject:Account (triggersOn,
declared).
- Criteria (verbatim, not evaluated):
(Account.Region__c equals '' OR Account.BillingCountry notEqual '').
- Actions:
- FieldUpdate action
Set_Region_From_Country
(references, declared) — shares the action target
with the rule above.
- Inactive flag. This rule is extracted but
active: false. Salesforce will not fire it; v1.3 still
lists it because the metadata exists. Migration target
or deletion candidate — decide per org policy.
v1.3 legacy-automation coverage: WorkflowRule (rules + alerts
- field updates + tasks + outbound messages, via the per-
object Workflow container), ApprovalProcess (entry criteria
- step chain), AssignmentRule / AutoResponseRule /
EscalationRule (per-object container with ruleEntry list),
DuplicateRule + MatchingRule (the duplicate-prevention
pair), EmailTemplate (text / html / visualforce / custom),
Letterhead (theme reference). Rule → action targets resolve
through declared edges; targets outside the extracted set
surface as "target not in extracted set" rather than being
silently dropped. Migration disclosure: Salesforce has
superseded WorkflowRule and Process Builder with Flow for
most use cases (Workflow Rules retire 2024+, Process
Builder retired). v1.3 still extracts them honestly because
real orgs still ship them in production, but when planning
changes consider whether the rule should move to Flow
before extending it.
Every rule is named with its canonical id. Every edge cites its
confidence. The inactive rule appears with its active: false
flag called out. The criteria text is surfaced verbatim, not
evaluated. The migration disclosure is fixed-form and unskippable.
Boundary disclosure
v1.3's legacy-automation coverage has well-defined gaps. The
fixed-form boundary disclosure (which the worked example above
embeds verbatim) surfaces this list once per response. The list
below documents what's behind each gap so Claude can answer
follow-ups without paraphrasing.
Workflow rule criteria evaluation. A WorkflowRule's
<criteriaItems> or <formula> is extracted as a property
string. v1.3 does not evaluate it against any record. "Does
this rule fire for this Account?" is record-level. Direct
the admin to Setup → Workflow Rules → {rule} → Test for
per-record evaluation.
Time-dependent workflow actions. Workflow rules carry
<workflowTimeTriggers> that schedule actions for some offset
(hours/days) after a criteria match. v1.3 surfaces the trigger
count via the node's properties.timeTriggerCount and the
declarative shape via properties.timeTriggers[] (each entry:
timeLength, timeUnit, offsetFromField, actionCount) —
all confidence declared. It does not model whether or
when a trigger fires (the offsetFromField offset is measured
from a record's field value, which is record-level), nor the
per-trigger action chain or the scheduled-action queue. Direct
the admin to Setup → Time-Based Workflow → View Pending
Actions for the live queue.
Per-rule immediate action counts. A WorkflowRule's IMMEDIATE
<actions> are counted by <type> into
properties.fieldUpdateCount, properties.outboundMessageCount,
and properties.taskCreationCount (all confidence declared).
These count the rule's consumed actions, NOT the top-level
<fieldUpdates> / <outboundMessages> / <tasks> DEFINITION
collections (a rule may consume only a subset of them), and NOT
the time-trigger nested actions (those stay under
timeTriggers[].actionCount). process_builder_migration_candidates
reads these to score migration complexity.
Approval process routing per step. ApprovalProcess
approver evaluation depends on the approver type
(userHierarchyField walks User.ManagerId per submitter,
relatedUser reads a User lookup on the submitted record,
queue and group are bundles). v1.3 stores the approver
type and name as properties but does not resolve the
approver per record. The skill walks the declared approver
chain (the references edges); the runtime approver is
record-level.
Duplicate rule fuzzy matching. MatchingRule's
<matchingMethod> (Exact, Fuzzy:Company Name, etc.) and
<blankValueBehavior> are extracted as string properties.
The actual fuzzy-match algorithm is opaque Salesforce platform
code. v1.3 lists the field-and-method recipe; it cannot
predict whether two specific records will collide.
Duplicate rule <duplicateRuleFilter>. The boolean filter
that scopes which records the rule applies to (e.g., "only
active leads") is stored as a string property and not
evaluated. Per-record applicability is out of scope.
EmailTemplate body merge fields. EmailTemplate bodies
contain {!Field_Reference} merge tokens. v1.3 stores the
body as a string property but does not tokenize merge
fields into per-field references edges (deferred to v1.4,
parallel with LWC/VF template-reference work). If the admin
asks "which fields does this template merge in?", surface the
body verbatim and direct them to read it themselves; do not
attempt to resolve {!Contact.FirstName} to
CustomField:Contact.FirstName.
Inactive rules. Workflow rules, approval processes,
assignment / auto-response / escalation rules, and duplicate
rules all carry an <active> / <isActive> / <ruleStatus>
property. v1.3 extracts the flag and the rule itself
regardless; always cite the active state when surfacing a
rule, and flag inactive rules explicitly. An inactive rule
isn't firing, but it's still part of the org's metadata
surface area and counts for naming-collision and
migration-target reasoning.
OutboundMessage action targets. WorkflowRule actions of
type OutboundMessage reference the OutboundMessage by
name. The OutboundMessage metadata itself is not extracted
as a node in v1.3 (deferred to v1.5 integration-topology).
The references edge dangles by design — surface it as
"target not in extracted set" and direct the admin to Setup
→ Outbound Messages.
WorkflowTask action targets. Same treatment as
outboundMessages — the rule's action references the task by
name; the WorkflowTask metadata isn't a node in v1.3. Edge
dangles, surfaced verbatim.
OrgWideEmailAddress. Referenced by AutoResponseRule's
<senderEmail> and WorkflowAlert's <senderAddress>. v1.3
stores the address as a string property; the
OrgWideEmailAddress metadata is deferred to v1.5. Surface the
address verbatim; do not attempt to resolve it to an
organization-wide email node.
EmailFolder hierarchy. Templates live under
email/{Folder}/{Template}.email-meta.xml; the folder is
captured in the canonical id (EmailTemplate:{Folder}. {Template}) but the EmailFolder itself is not a node.
Folder-level access permissions are a Profile / PermissionSet
concern outside v1.3.
Process Builder. Process Builder is metadata-shaped as a
Flow (<processType>Workflow</processType> in the Flow XML).
v0.1's Flow extractor already covers it; v1.3 does not
duplicate that coverage. If the admin asks "is this a Process
Builder or a Workflow Rule?", point to the Flow: vs
WorkflowRule: id prefix; the type is the answer.
Inline criteria-field references. WorkflowRule,
AssignmentRule, EscalationRule, and AutoResponseRule ship
<criteriaItems> with <field> references. Where the COUNT lands
differs per family, and getting this wrong reads as "no criteria":
- WorkflowRule —
criteriaItemCount is a NODE property
(properties.criteriaItemCount), alongside formula and
booleanFilter.
- AssignmentRule — per-
ruleEntry, on the EDGE: each outgoing
references edge carries entryIndex, criteriaItemCount,
hasFormula. There is no node-level count.
- AutoResponseRule — per-
ruleEntry, on the outgoing
sendsEmail EDGE (entryIndex, criteriaItemCount,
hasFormula). There is no node-level count.
- EscalationRule — no
criteriaItemCount at all, on the
node or on any edge. Its node properties are active,
ruleEntryCount, actionCount, conditions. Say the count is
unavailable for escalation rules; do not report a missing count
as zero criteria.
None of the four emit per-field references edges from the rule to
each criterion field, and none store the raw criteria field-and-value
text as a node property — read the conditions mirror /
ConditionalContext nodes for the expression instead.
Workflow flow-trigger Apex (callsApex floor). When a
WorkflowRule action is of type Apex, the rule node has a
callsApex edge to the named ApexClass. The v0.1
Apex-source extractor handles the receiving class; v1.3 just
extends the producer side.
Migration to Flow. Workflow Rules and Process Builder
retirement is in flight (Workflow 2024+, Process Builder
already retired). v1.3 extracts both because real orgs ship
them; the skill's migration disclosure recommends Flow as the
modern replacement but does not auto-migrate.
Treat dangling edges as a flag for manual investigation —
they're evidence that a target was deleted, renamed, or never
extracted, and the admin needs the pointer to act on it. Treat
inactive rules as still-part-of-the-metadata-surface — they
don't fire, but they're a migration candidate or a cleanup
candidate. Treat criteria text as documentation, never as
an evaluator — v1.3 reads it verbatim and stops.
Anti-patterns
| Mistake |
Why it's wrong |
| Confusing WorkflowRule with Flow. |
WorkflowRule and Flow are different metadata types — WorkflowRule:Account.X vs Flow:Account_X. The skill's audience is admins working on the legacy declarative tier; if the user names a Flow, defer to the Flow-handling skill (or route to a Flow-narration refusal). Never label a WorkflowRule: id as a "flow" in plain English. |
Not citing the active / isActive / ruleStatus property. |
An inactive WorkflowRule is extracted but not firing. The admin's first question after "what does this do" is "is this still on?" — the active flag is load-bearing. Cite it for every rule, every time. |
| Treating extracted criteria as evaluated. |
v1.3 reads <criteriaItems> and <formula> as text, not as a predicate. Saying "this rule fires when X = 'Tier 1'" is fine; saying "this rule will fire for this Account because its Type is 'Tier 1'" is fabricating a record-level evaluation. Surface the criteria verbatim, name what would have to be true for it to fire, and refuse the per-record half. |
| Silently dropping a dangling action target. |
The Q35 honesty anchor verbatim. A WorkflowRule whose fieldUpdate names a CustomField not in the extracted set is evidence that the field has been removed or renamed. Surface the dangling edge explicitly; never collapse it into the report's empty space. |
| Confusing DuplicateRule with MatchingRule. |
DuplicateRule is the policy ("block save on insert when X looks like Y"); MatchingRule is the recipe ("compare fields A, B with methods M"). They're separate types connected by a references edge from duplicate to matcher. Walking the chain is the answer to "what fields does this duplicate rule check?"; collapsing the two loses the comparison logic. |
| Skipping the ApprovalProcess approver chain. |
ApprovalProcess has <approvalSteps>[*].<assignedApprover> — that chain is the answer to "who approves this?". Walking the chain step-by-step and naming each approver (Role / Group / User / Queue) is the legible trace the admin came for. Naming only the process and stopping at "an approval process" misses the entire point. |
| Translating EmailTemplate's canonical id with an object prefix. |
EmailTemplate is the outlier — EmailTemplate:{Folder}.{Template}, NOT EmailTemplate:{Object}.{Template}. The template's folder is the parent, not the object the template concerns. The body merges field references for whatever object the calling rule operates on. |
| Walking past Letterhead. |
Letterhead is a leaf — it has zero outgoing edges. When the admin asks "what does this letterhead do?", surface its properties (topLineColor, bodyColor, available, description) and the EmailTemplates that reference it (via sfi.get_edges with direction: 'in'). Don't go further; there's nothing to walk to. |
| Treating the metadata definition as runtime behavior. |
"v1.3 lists what's configured" is the constitutional axis. If the admin asks "is this workflow firing right now?", the honest answer is "I can tell you what it's configured to do; whether it fires depends on record-level data v1.x doesn't have." Refuse the runtime half and surface the configuration half. |
| Recommending Flow migration without the impact chain first. |
The migration disclosure is a footer, not a replacement for the impact analysis. An admin planning a migration without the full target list (action references, sendsEmail targets, callsApex targets) will produce an incomplete Flow. Walk the chain, then recommend migration. |
| Conflating "active rule" with "still relevant" or "live." |
An active rule is firing; that's a metadata fact. Whether it's still correct — whether the criteria still match the org's business logic — is judgment, not metadata. Don't paper over the gap. |
Citing parsed or heuristic confidence on a v1.3 edge. |
v1.3's new emission set is all declared: sendsEmail, the reused triggersOn / references / callsApex / parentOf instances. The extractor reads directly from XML elements. Don't introduce uncertainty where the metadata is plain. |
| Skipping the boundary disclosure on a clean impact report. |
The disclosure protects against the wrong mental model. A clean report that lists every action target still has to coexist with un-modeled gaps (OutboundMessage, WorkflowTask, OrgWideEmailAddress, EmailTemplate merge fields, record-level evaluation). Always disclose. |
Example interactions
Happy path — "what workflow rules touch Account?"
See Reporting format above for the full transcript. Summary:
parse to (type: 'WorkflowRule', parentId: 'CustomObject:Account'),
enumerate via sfi.list_components, walk each rule's impact via
sfi.get_impact, present as (active flag → triggers on → criteria text → actions → sendsEmail targets), append the
fixed-form boundary disclosure with the migration paragraph.
Cross-reference — "which approval processes use this email template?"
User: "Which rules send the EmailTemplate:Finance.CreditApprovalNeeded
email template?"
Claude's flow:
- Parse → the question is a
sendsEmail inversion. Target id
is EmailTemplate:Finance.CreditApprovalNeeded (Folder.Template
format).
- Call
sfi.get_edges with { nodeId: 'EmailTemplate:Finance.CreditApprovalNeeded', direction: 'in', edgeType: 'sendsEmail' }.
- Receive (illustrative):
{
"data": {
"edges": [
{
"fromId": "ApprovalProcess:Account.Credit_Review",
"toId": "EmailTemplate:Finance.CreditApprovalNeeded",
"edgeType": "sendsEmail",
"confidence": "declared",
"properties": {
"actionContext": "approvalSteps[1].notificationTemplate",
"stepLabel": "Finance VP Approval"
}
}
]
}
}
- Respond:
One rule sends EmailTemplate:Finance.CreditApprovalNeeded:
ApprovalProcess:Account.Credit_Review (sendsEmail,
declared) — sent from approval step step_2_finance_vp
(approvalSteps[1].notificationTemplate, label "Finance VP
Approval"). The step's approver is Role:Finance_VP with
Queue:Credit_Review_Queue as an alternate approver.
The process's other steps (step_1_sales_manager) and its
top-level <emailTemplate> element point at different
templates; this query was scoped to
EmailTemplate:Finance.CreditApprovalNeeded only. If you want
the full template inventory for this process, ask "which email
templates does ApprovalProcess:Account.Credit_Review use?" and
I'll walk the outgoing edges.
v1.3 legacy-automation coverage: [fixed-form boundary
disclosure follows].
Migration recommendation — "should I migrate this workflow to Flow?"
User: "Should I migrate WorkflowRule:Account.Notify_Sales_On_New_Tier1
to Flow?"
Claude's flow:
- Walk the impact chain first via
sfi.get_impact on the
rule's id. Surface the full target list (the triggersOn
parent object, the references edges to the action targets,
the sendsEmail edge to the template, any callsApex edges).
- Then add the migration paragraph.
- Respond:
Impact chain for WorkflowRule:Account.Notify_Sales_On_New_Tier1
(active: true, triggerType: onCreateOnly):
- Triggers on
CustomObject:Account (triggersOn,
declared).
- Criteria (verbatim):
Account.Type equals 'Tier 1'.
- Actions:
- Alert action
Email_Sales_Team (references, declared)
→ sendsEmail to EmailTemplate:Sales.WelcomeEmail
(declared). Recipient: Group:Sales_Group. Sender:
CurrentUser.
- FieldUpdate action
Set_Region_From_Country (references,
declared) — sets CustomField:Account.Region__c via the
formula IF(BillingCountry = "US", "Americas", "EMEA").
Migration considerations:
Salesforce has superseded WorkflowRule and Process Builder
with Flow for most use cases (Workflow Rules retire 2024+,
Process Builder retired). v1.3 still extracts WorkflowRules
honestly because real orgs still ship them in production —
but when planning changes, consider whether the rule should
move to Flow before extending it. Migration considerations
grounded in the impact chain above:
- The criteria predicate
Account.Type equals 'Tier 1'
translates to a Record-Triggered Flow with a start
condition $Record.Type = 'Tier 1' and the On Create
trigger type matching onCreateOnly.
- The Alert action's
EmailTemplate:Sales.WelcomeEmail
target must be reproduced in the Flow via a **
…(truncated)
1---2name: admin-legacy-automation3description: Answers admin questions about Salesforce's legacy automation layer: "what workflow rules touch Account", "which approval processes are still active", "what assignment rule routes Cases", "is this workflow being replaced by a flow", "which fields does this duplicate rule check", "what emails go out for this approval", "show me the auto-response rules for Lead". Calls `sfi.get_impact` and `sfi.get_edges` to find workflow rules, approval processes, assignment/auto-response/escalation rules, duplicate/matching rules, email templates, and letterheads — plus the `sendsEmail`, `triggersOn`, and `references` edges that connect them. Discloses the v1.3 boundary honestly: workflow rules and processes are extracted in v1.3, but their actual execution semantics (criteria evaluation, time-dependent actions, approval routing) need record-level data v1.x doesn't have. Recommends Flow migration honestly where applicable.4---56# Admin legacy automation78## Overview910This skill is the admin persona's companion to v1.3's coverage11extension of `sfi.get_impact`. v1.3 does not introduce a new12headline tool — it makes the existing impact graph aware of nine13new ComponentTypes (`WorkflowRule`, `ApprovalProcess`,14`AssignmentRule`, `AutoResponseRule`, `EscalationRule`,15`DuplicateRule`, `MatchingRule`, `EmailTemplate`, `Letterhead`)16and one new EdgeType (`sendsEmail`). The admin's first-line17question for these types is "what does this rule do?", "what18emails fire when X happens?", or "is this still active in our19org?" — and the honest answer is to walk the edges the extractors20emit from XML and present them as a structured rule → action →21target timeline.2223The graph consumes one new EdgeType and reuses three existing24ones. The new edge is `sendsEmail` (a rule declares it will send25the named EmailTemplate; direction: `{WorkflowRule |26ApprovalProcess | AutoResponseRule} → EmailTemplate`,27`confidence: declared`). The reused edges, all `declared` in28v1.3's emission set, are `triggersOn` (rule → parent29`CustomObject`), `references` (rule → action target or approver30or assignee or matching rule), and `parentOf` (one-file-many-31rules container relationship from `CustomObject` to each per-32rule node). The boundary that matters for admins: **v1.3 lists33what's configured, it does not simulate runtime.** Workflow rule34criteria, time-dependent actions, approval routing per record,35and duplicate fuzzy-match decisions all need record-level data36v1.x doesn't have. The skill surfaces this gap explicitly rather37than fabricating an evaluated answer.3839## When to fire4041Fire this skill on legacy-automation phrasing. Concrete triggers:4243- **"What workflow rules touch / fire on object Y?"** — "what44 workflow rules touch Account?", "which workflows fire on a Case45 update?", "show me the workflows on Opportunity.".46- **"Which approval processes are still active?"** — "which47 approval processes are active on Opportunity?", "list the48 approval processes for Account.", "are the discount approvals49 still in use?".50- **"What assignment rule routes Cases / Leads?"** — "what's the51 assignment rule for Lead?", "how does Case routing work?", "show52 me the lead-routing rules.".53- **"Is this workflow being replaced by a flow?"** — "should I54 migrate `Notify_Sales_On_New_Tier1` to Flow?", "is this55 workflow legacy?", "what do I lose if I move this rule to56 Flow?".57- **"Which fields does this duplicate rule check?"** — "what58 fields does `DuplicateRule:Account.Standard_Duplicate` compare?",59 "show me the matching rule behind the lead duplicate check.".60- **"What emails go out for this approval / rule?"** — "which61 email template fires when the discount approval is submitted?",62 "what auto-response goes out for a web lead?", "which approvals63 send the `Sales/WelcomeEmail` template?".64- **"Show me the auto-response rules for Lead / Case."** — "what65 auto-responses do we have on Lead?", "list the autoresponse66 rules.".67- **"What's the escalation chain on Case?"** — "show me the68 escalation rules for Case.", "what queue does P1 escalate to?",69 "when does a P1 case escalate?".70- **"Which rules use the `{TemplateName}` email template?"** —71 inversion question; walks the `sendsEmail` edge in the `in`72 direction.73- **"What does this letterhead get used by?"** — leaf-node usage74 query; walks the `references` edge inward to EmailTemplate75 nodes.76- **"Why did this account get assigned to this queue?"** — the77 *configuration* answer is the AssignmentRule that names the78 queue; the *record-level* answer needs live data and is out of79 scope. Surface both halves.80- **"What's the criteria for this rule?"** — surface the extracted81 criteria count from wherever that family puts it (node property on82 WorkflowRule; per-entry EDGE property on AssignmentRule /83 AutoResponseRule; absent entirely on EscalationRule) plus the rule's84 `formula` where present. The **evaluation** of the criteria against a85 record needs record-level data and is out of scope.8687## When NOT to fire8889Defer to another skill when:9091- **The user asks "what breaks if I change X?"** — cross-component92 impact analysis. Defer to `architect-impact-analysis` →93 `sfi.get_impact`. (That skill already walks the v1.3 edges; it94 just frames the answer for architects rather than admins.)95- **The user asks "why can't user X see this record?"** — visibility,96 not automation. Defer to `admin-sharing-troubleshooting` →97 `sfi.why_cant_user_see_record`.98- **The user asks "what layout does user X see?"** — page-layout99 routing. Defer to `admin-page-layout-routing` →100 `sfi.layout_for_user`.101- **The user asks "where is this field used in Apex?"** —102 code-side reference question. Defer to `developer-apex-refactor`103 → `sfi.find_code_usages`.104- **The user asks "what does this flow do?"** — Flow narration is105 outside v1.3 (and outside the legacy-automation skill regardless;106 Flow is the *modern* automation tier). Defer to107 `business-user-orientation`'s process-explanation intent, which108 refuses to narrate Flow XML as English. If the user wants to know109 whether a *workflow rule* has been replaced by a Flow on the same110 object, this skill *can* help — list both and call out that the111 workflow rule is still active per its `<active>true</active>`112 property.113- **The user asks for record-level evaluation** ("does this114 workflow rule actually fire on Account 001xx12345?", "is this115 approval process triggered for this specific opportunity?").116 v1.3 lists what's configured; it doesn't simulate execution.117 Refuse honestly; direct the admin to **Setup → Workflow Rules**118 or **Setup → Approval Processes** in the Salesforce UI, or to a119 live `sf data query` against their org.120- **The user wants to refresh, init, or check vault status.** Fire121 `refreshing-the-org-vault`, `/sfi-init`, or `pre-flight-checks`.122- **The user names an automation type outside v1.3's nine** —123 `OutboundMessage`, `WorkflowTask`, `OrgWideEmailAddress`, or124 Process Builder as a standalone type (Process Builder is125 metadata-shaped as a `Flow`, covered by v0.1's Flow extractor).126 Say so plainly and name the boundary; offer the closest partial127 if one exists (e.g., the rule's action `references` the outbound128 message by name even though the OutboundMessage node itself isn't129 extracted).130131## Steps132133Walk these in order. Each step has a definite output that feeds134the next.135136### Step 1 — Parse the question into `(object, automation-type, [rule-name])`137138The user almost never types a canonical id. They say "the139workflows on Account", "the discount approval", "the lead140auto-response". Translate to one or both of:141142- A **type-scoped enumeration** when the user asks "which / what143 / show me" — translate "the workflow rules on Account" to144 `(type: 'WorkflowRule', parentId: 'CustomObject:Account')`.145- A **specific rule id** when the user names a rule — translate146 "the Notify Sales workflow" to a search call, then to147 `WorkflowRule:Account.Notify_Sales_On_New_Tier1` (the canonical148 form). The format per type is:149150| Type | Canonical id format | Example |151|---|---|---|152| `WorkflowRule` | `WorkflowRule:{ObjectApiName}.{RuleFullName}` | `WorkflowRule:Account.Notify_Sales_On_New_Tier1` |153| `ApprovalProcess` | `ApprovalProcess:{ObjectApiName}.{ProcessName}` | `ApprovalProcess:Opportunity.Discount_Approval` |154| `AssignmentRule` | `AssignmentRule:{ObjectApiName}.{RuleFullName}` | `AssignmentRule:Lead.Standard_Lead_Routing` |155| `AutoResponseRule` | `AutoResponseRule:{ObjectApiName}.{RuleFullName}` | `AutoResponseRule:Lead.Standard_Web_To_Lead` |156| `EscalationRule` | `EscalationRule:{ObjectApiName}.{RuleFullName}` | `EscalationRule:Case.P1_Case_Escalation` |157| `DuplicateRule` | `DuplicateRule:{ObjectApiName}.{RuleName}` | `DuplicateRule:Account.Standard_Duplicate` |158| `MatchingRule` | `MatchingRule:{ObjectApiName}.{RuleFullName}` | `MatchingRule:Account.Account_Name_And_Domain` |159| `EmailTemplate` | `EmailTemplate:{Folder}.{TemplateName}` | `EmailTemplate:Sales.WelcomeEmail` (note: folder, not object) |160| `Letterhead` | `Letterhead:{LetterheadName}` | `Letterhead:Corporate` |161162EmailTemplate is the outlier — its canonical id keys off the163template's folder, not an object (templates live under164`email/{Folder}/{Template}.email-meta.xml`). Letterhead is a leaf165node with zero outgoing edges (it has no `references` to anything;166only EmailTemplates `reference` *it*).167168If the user names a rule by display label rather than API name169("the discount workflow"), call `sfi.search_components({ query:170'discount workflow', types: ['WorkflowRule'] })` to translate171before firing the impact tool. Don't guess — workflow rule names172are bespoke per org. A canonical id that matches nothing at all now173returns `error.kind: 'component-not-found'` rather than an empty174slice, but an id that resolves to the WRONG component still answers175confidently about that component, and nothing in the payload will say176so.177178If the user supplies a record-level shape ("does the discount179approval fire on opportunity 006xx12345?"), **refuse the record-180level half** and translate to the configuration question181explicitly: "I can tell you what `ApprovalProcess:Opportunity.182Discount_Approval` is configured to do — entry criteria, approver183chain, email templates. v1.x can't tell you whether *that184specific record* would trigger it; check the record's185`Approval History` related list in Salesforce for that."186187### Step 2 — Enumerate or fetch the target node(s)188189For type-scoped enumerations, call `sfi.list_components`:190191```json192{ "type": "WorkflowRule", "parentId": "CustomObject:Account" }193```194195This returns every `WorkflowRule:Account.*` node sorted by id.196Surface the list with each rule's `active` property (read from197`properties.isActive` or `properties.active` on the node) so the198admin can immediately see which rules are live and which are199inactive but still extracted.200201For specific rule queries, call `sfi.get_component` with the202canonical id to fetch the node's full properties (criteria203formula, action list, business-hours reference, etc.) before204walking edges. This gives the timeline its context.205206### Step 3 — Walk the impact / edge graph207208For a **rule-impact** question ("what does this rule touch?"),209call `sfi.get_impact` with the rule's id:210211```json212{ "componentId": "WorkflowRule:Account.Notify_Sales_On_New_Tier1", "hops": 2 }213```214215The response carries `impact.nodes` (the rule plus everything it216touches) and `impact.edges`. Walk the edges and bucket by217`edgeType`:218219- **`triggersOn`** — the parent object the rule fires on.220- **`references`** — the rule's action targets (named221 WorkflowAlert / FieldUpdate / OutboundMessage / Task, approver222 Role / Group / User, assignee Queue / User, matching-rule223 pointer from DuplicateRule).224- **`sendsEmail`** — the EmailTemplate the rule's alert /225 notification step sends.226- **`callsApex`** — when a WorkflowRule action invokes an Apex227 class via a `<flowTrigger>` / `<workflowAction>` of type228 `Apex`, the edge points at the named `ApexClass`.229- **`parentOf`** — the container relationship from230 `CustomObject:{Object}` to each per-rule node; mostly231 scaffolding, but surface it once for the record so the admin232 knows the parent is `CustomObject:Account` (or whichever).233234For an **inversion** question ("which rules send this email235template?"), use `sfi.get_edges`:236237```json238{239 "nodeId": "EmailTemplate:Sales.WelcomeEmail",240 "direction": "in",241 "edgeType": "sendsEmail"242}243```244245The response lists every `WorkflowRule`, `ApprovalProcess`, or246`AutoResponseRule` node with an outgoing `sendsEmail` edge into247the named template. Sort by rule type for legibility.248249For an **approver chain** question ("who approves the discount250process?"), use `sfi.get_edges`:251252```json253{254 "fromId": "ApprovalProcess:Opportunity.Discount_Approval",255 "direction": "out",256 "edgeType": "references"257}258```259260The response lists every `references` edge to a Role, Group,261User, or Queue named in `<approvalSteps>[*].<assignedApprover>`.262Walk the list in step order (the extractor preserves it) and263present each step with its label and approver.264265### Step 4 — Follow `sendsEmail` to the EmailTemplate body266267When a rule has a `sendsEmail` edge, the natural follow-up268question is "what does that email actually say?" Call269`sfi.get_component` on the EmailTemplate id and surface:270271- `properties.subject` — the email's subject line, verbatim.272- `properties.templateType` — `text`, `html`, `visualforce`, or273 `custom`. (The XML element is `<type>`; the extracted property is274 `templateType`. There is no `properties.type`.)275- **There is no `properties.body` and no `properties.content`.** The276 extractor does not store the body string at all — it stores277 `properties.bodyLength`, a character count (always `0` for278 `visualforce` templates, whose body lives in the referenced VF page).279 If the admin needs the body text, point them at the source file, or280 read it via the `sourcePath` on the node.281- **Merge tokens ARE tokenized.** The v3.0 body-merge scan parses the282 `<content>` body for `{!Object.Field}` and conditional283 `{!IF(... Object.Field ...)}` merges. Each distinct field yields a284 `references` edge with `confidence: 'parsed'`, `properties.role:285 'body-merge'`, `properties.mergeContext` (the verbatim `{!...}`286 token) and `properties.conditional`. The node mirrors this as287 `properties.mergeFields` (canonical `CustomField:` ids) and288 `properties.referencedObjects`. Do NOT tell the admin merge tokens289 are un-tokenized — surface the resolved fields.290- `properties.richTemplateSyntaxDetected` — true when at least one291 merge appeared inside a function call. When true, disclose the real292 boundary: the field REFERENCES are captured; the FIRING LOGIC of the293 conditional is not.294- `properties.letterheadName` — the named Letterhead, if any. (The XML295 element is `<letterhead>`; the extracted property is296 `letterheadName`. There is no `properties.letterhead`.)297298If the template's `properties.letterheadName` is non-null, the299EmailTemplate node has a `references` edge to the named300Letterhead. Surface the letterhead's name for context; don't walk301further (Letterhead is a leaf — top/bottom/header colors and302styling, nothing more).303304### Step 5 — Present as a structured timeline305306The raw response is a flat list of nodes and edges. The human307reading needs a grouped timeline: **rule → trigger → criteria →308actions → targets**. Walk the rule's edges and present in this309order:3103111. **What it is.** Rule type, canonical id, parent object,312 `active` property. Surface inactive rules explicitly — they're313 still extracted, but the admin needs to know "this rule is in314 the metadata but `active: false`."3152. **When it fires.** Trigger type for WorkflowRule316 (`onCreateOnly`, `onAllChanges`, etc.), entry criteria for317 ApprovalProcess (formula text or `criteriaItems`), action318 timing for EscalationRule — read `minutesToEscalation` off the319 rule's outgoing `references` / `sendsEmail` EDGES, which is where320 the extractor puts it; it is NOT a node property. Do NOT report321 `escalationStartTime`: the extractor validates it (against the322 real Salesforce enum, `CaseCreation` / `CaseLastModified` — no323 other value exists) but does not store it, so the vault cannot324 tell you which one a rule uses. Say so rather than guessing.325 `actionOnInsert` / `actionOnUpdate` for DuplicateRule.3263. **Criteria summary.** Surface the criteria count from the RIGHT327 place for the family (WorkflowRule: `properties.criteriaItemCount`;328 AssignmentRule / AutoResponseRule: the per-`ruleEntry`329 `criteriaItemCount` on the outgoing `references` / `sendsEmail`330 edge; EscalationRule: **unavailable** — say so) plus331 `properties.formula` where it exists. There is no332 `properties.criteriaItems` node property on any of them. Do **not**333 evaluate the criteria — that's record-level. The admin reads the334 predicate themselves.3354. **Actions.** Bucket by action category (Alert / FieldUpdate /336 Apex / OutboundMessage / Task for WorkflowRule;337 initialSubmission / finalApproval / finalRejection /338 recallActions for ApprovalProcess; escalationAction[] for339 EscalationRule; ruleEntry[] for AssignmentRule /340 AutoResponseRule). For each action, name the target via its341 `references` or `sendsEmail` edge.3425. **Targets.** For each action target, cite the canonical id343 (`EmailTemplate:Sales.WelcomeEmail`, `Queue:Tier2_Support`,344 `MatchingRule:Account.Account_Name_And_Domain`, etc.) and the345 confidence (always `declared` for v1.3 emission).3466. **Disclosure.** "v1.3 lists what's configured; doesn't347 simulate runtime."348349**Do not silently drop steps.** An inactive rule, an empty action350list, a dangling action target — they're all signal. The admin's351trust hinges on every extracted edge appearing in the report.352353### Step 6 — Honest disclosure when an edge dangles354355The v1.0 graph store tolerates dangling edges. When a356WorkflowRule action references a target the extractor cannot357resolve — e.g., the rule's `<fieldUpdates>[*].<field>` names358`Region__c` but `CustomField:Account.Region__c` is not in the359extracted set — the edge is emitted with the target id intact and360the consumer sees "target not in extracted set" when walking.361362Surface this verbatim, **never silently drop it**:363364> The workflow rule's fieldUpdate references365> `CustomField:Account.Region__c`, which is **not in the366> extracted set**. Either the field has been removed from the org367> since the last refresh, the field name was renamed in metadata368> and the workflow points at the old name, or the extraction369> missed it. Verify in **Setup → Object Manager → Account →370> Fields**.371372This is the v1.3 honesty anchor (Q35). The dangling edge is373*evidence*, not a footnote — it's how the admin learns that the374workflow rule is pointing at a field that no longer exists in the375expected place.376377### Step 7 — Append the v1.3 boundary disclosure378379Every response from this skill ends with the fixed-form boundary380disclosure (next section). When the response surfaces a dangling381edge, restate it; otherwise it appears once at the end.382383### Step 8 — When the question is migration-shaped, add the Flow recommendation384385If the admin asked "should I migrate this workflow to Flow?",386"is this workflow legacy?", or "what would I lose by moving this387to Flow?", **walk the impact chain first** so the migration388conversation has the full dependency picture. Then add the389migration paragraph:390391> Salesforce has superseded WorkflowRule and Process Builder392> with Flow for most use cases. Workflow Rules are in the multi-393> release retirement track (retirement began in 2024+); Process394> Builder is already retired. v1.3 still extracts WorkflowRules395> honestly because real orgs still ship them in production —396> but when planning changes, consider whether the rule should397> move to Flow before extending it. **Migration considerations398> grounded in the impact chain above:**399>400> - Every `references` / `sendsEmail` / `callsApex` target listed401> above must be reproduced in the new Flow.402> - Time-dependent workflow actions (`<workflowTimeTriggers>`)403> require Scheduled Paths in the migrating Flow.404> - The criteria predicate translates to a Flow start condition405> or a Decision element.406> - The Salesforce **Migrate to Flow** tool (Setup → Workflow407> Rules → Migrate to Flow) handles most cases mechanically;408> complex Time-Dependent Actions may require manual rewrite.409410The migration disclosure is a *footer*, not a replacement for411the impact analysis. An admin planning a migration without the412full target list will produce an incomplete Flow.413414## Reporting format415416Use the synthetic-v1.3 fixture for the worked example. Suppose417the admin asks:418419> "What workflow rules touch Account?"420421Claude's flow:4224231. **Parse** → `(type: 'WorkflowRule', parentId:424 'CustomObject:Account')`.4252. **Enumerate** via `sfi.list_components` (illustrative):426427```json428{429 "data": {430 "components": [431 {432 "id": "WorkflowRule:Account.Notify_Sales_On_New_Tier1",433 "type": "WorkflowRule",434 "properties": {435 "active": true,436 "triggerType": "onCreateOnly",437 "criteriaItemCount": 1438 }439 },440 {441 "id": "WorkflowRule:Account.Backfill_Region_On_Edit",442 "type": "WorkflowRule",443 "properties": {444 "active": false,445 "triggerType": "onAllChanges",446 "criteriaItemCount": 2447 }448 }449 ]450 }451}452```4534543. **Walk each rule's impact** via `sfi.get_impact` on each id.4554. **Respond:**456457> Workflow rules on `CustomObject:Account` (extracted from458> `workflows/Account.workflow-meta.xml`):459>460> **`WorkflowRule:Account.Notify_Sales_On_New_Tier1`** —461> `active: true`, `triggerType: onCreateOnly`,462> `criteriaItemCount: 1`.463> - **Triggers on** `CustomObject:Account` (`triggersOn`,464> `declared`).465> - **Criteria** (verbatim, not evaluated): `Account.Type466> equals 'Tier 1'`. v1.3 lists what's configured; the467> per-record evaluation needs record data.468> - **Actions:**469> - Alert action `Email_Sales_Team` (`references`,470> `declared`) → `sendsEmail` to471> `EmailTemplate:Sales.WelcomeEmail` (`declared`).472> - FieldUpdate action `Set_Region_From_Country`473> (`references`, `declared`) — sets474> `CustomField:Account.Region__c` via the formula475> `IF(BillingCountry = "US", "Americas", "EMEA")`.476>477> **`WorkflowRule:Account.Backfill_Region_On_Edit`** —478> `active: false`, `triggerType: onAllChanges`,479> `criteriaItemCount: 2`.480> - **Triggers on** `CustomObject:Account` (`triggersOn`,481> `declared`).482> - **Criteria** (verbatim, not evaluated):483> `(Account.Region__c equals '' OR Account.BillingCountry484> notEqual '')`.485> - **Actions:**486> - FieldUpdate action `Set_Region_From_Country`487> (`references`, `declared`) — shares the action target488> with the rule above.489> - **Inactive flag.** This rule is extracted but490> `active: false`. Salesforce will not fire it; v1.3 still491> lists it because the metadata exists. Migration target492> *or* deletion candidate — decide per org policy.493>494> v1.3 legacy-automation coverage: WorkflowRule (rules + alerts495> + field updates + tasks + outbound messages, via the per-496> object Workflow container), ApprovalProcess (entry criteria497> + step chain), AssignmentRule / AutoResponseRule /498> EscalationRule (per-object container with ruleEntry list),499> DuplicateRule + MatchingRule (the duplicate-prevention500> pair), EmailTemplate (text / html / visualforce / custom),501> Letterhead (theme reference). Rule → action targets resolve502> through declared edges; targets outside the extracted set503> surface as "target not in extracted set" rather than being504> silently dropped. **Migration disclosure:** Salesforce has505> superseded WorkflowRule and Process Builder with Flow for506> most use cases (Workflow Rules retire 2024+, Process507> Builder retired). v1.3 still extracts them honestly because508> real orgs still ship them in production, but when planning509> changes consider whether the rule should move to Flow510> before extending it.511512Every rule is named with its canonical id. Every edge cites its513confidence. The inactive rule appears with its `active: false`514flag called out. The criteria text is surfaced verbatim, not515evaluated. The migration disclosure is fixed-form and unskippable.516517## Boundary disclosure518519v1.3's legacy-automation coverage has well-defined gaps. The520fixed-form boundary disclosure (which the worked example above521embeds verbatim) surfaces this list once per response. The list522below documents what's behind each gap so Claude can answer523follow-ups without paraphrasing.524525- **Workflow rule criteria evaluation.** A WorkflowRule's526 `<criteriaItems>` or `<formula>` is extracted as a property527 string. v1.3 does **not** evaluate it against any record. "Does528 this rule fire for *this* Account?" is record-level. Direct529 the admin to **Setup → Workflow Rules → {rule} → Test** for530 per-record evaluation.531- **Time-dependent workflow actions.** Workflow rules carry532 `<workflowTimeTriggers>` that schedule actions for some offset533 (hours/days) after a criteria match. v1.3 surfaces the trigger534 count via the node's `properties.timeTriggerCount` and the535 declarative shape via `properties.timeTriggers[]` (each entry:536 `timeLength`, `timeUnit`, `offsetFromField`, `actionCount`) —537 all confidence `declared`. It does **not** model whether or538 *when* a trigger fires (the `offsetFromField` offset is measured539 from a record's field value, which is record-level), nor the540 per-trigger action chain or the scheduled-action queue. Direct541 the admin to **Setup → Time-Based Workflow → View Pending542 Actions** for the live queue.543- **Per-rule immediate action counts.** A WorkflowRule's IMMEDIATE544 `<actions>` are counted by `<type>` into545 `properties.fieldUpdateCount`, `properties.outboundMessageCount`,546 and `properties.taskCreationCount` (all confidence `declared`).547 These count the rule's *consumed* actions, NOT the top-level548 `<fieldUpdates>` / `<outboundMessages>` / `<tasks>` DEFINITION549 collections (a rule may consume only a subset of them), and NOT550 the time-trigger nested actions (those stay under551 `timeTriggers[].actionCount`). `process_builder_migration_candidates`552 reads these to score migration complexity.553- **Approval process routing per step.** ApprovalProcess554 approver evaluation depends on the approver type555 (`userHierarchyField` walks `User.ManagerId` per submitter,556 `relatedUser` reads a User lookup on the submitted record,557 `queue` and `group` are bundles). v1.3 stores the approver558 type and name as properties but does **not** resolve the559 approver per record. The skill walks the *declared* approver560 chain (the `references` edges); the *runtime* approver is561 record-level.562- **Duplicate rule fuzzy matching.** MatchingRule's563 `<matchingMethod>` (`Exact`, `Fuzzy:Company Name`, etc.) and564 `<blankValueBehavior>` are extracted as string properties.565 The actual fuzzy-match algorithm is opaque Salesforce platform566 code. v1.3 lists the field-and-method recipe; it cannot567 predict whether two specific records will collide.568- **Duplicate rule `<duplicateRuleFilter>`.** The boolean filter569 that scopes which records the rule applies to (e.g., "only570 active leads") is stored as a string property and **not**571 evaluated. Per-record applicability is out of scope.572- **EmailTemplate body merge fields.** EmailTemplate bodies573 contain `{!Field_Reference}` merge tokens. v1.3 stores the574 body as a string property but does **not** tokenize merge575 fields into per-field `references` edges (deferred to v1.4,576 parallel with LWC/VF template-reference work). If the admin577 asks "which fields does this template merge in?", surface the578 body verbatim and direct them to read it themselves; do not579 attempt to resolve `{!Contact.FirstName}` to580 `CustomField:Contact.FirstName`.581- **Inactive rules.** Workflow rules, approval processes,582 assignment / auto-response / escalation rules, and duplicate583 rules all carry an `<active>` / `<isActive>` / `<ruleStatus>`584 property. v1.3 extracts the flag and the rule itself585 regardless; **always cite the active state** when surfacing a586 rule, and flag inactive rules explicitly. An inactive rule587 isn't firing, but it's still part of the org's metadata588 surface area and counts for naming-collision and589 migration-target reasoning.590- **`OutboundMessage` action targets.** WorkflowRule actions of591 type `OutboundMessage` `reference` the OutboundMessage by592 name. The OutboundMessage metadata itself is **not** extracted593 as a node in v1.3 (deferred to v1.5 integration-topology).594 The `references` edge dangles by design — surface it as595 "target not in extracted set" and direct the admin to **Setup596 → Outbound Messages**.597- **`WorkflowTask` action targets.** Same treatment as598 outboundMessages — the rule's action references the task by599 name; the WorkflowTask metadata isn't a node in v1.3. Edge600 dangles, surfaced verbatim.601- **`OrgWideEmailAddress`.** Referenced by AutoResponseRule's602 `<senderEmail>` and WorkflowAlert's `<senderAddress>`. v1.3603 stores the address as a string property; the604 OrgWideEmailAddress metadata is deferred to v1.5. Surface the605 address verbatim; do not attempt to resolve it to an606 organization-wide email node.607- **EmailFolder hierarchy.** Templates live under608 `email/{Folder}/{Template}.email-meta.xml`; the folder is609 captured in the canonical id (`EmailTemplate:{Folder}.610 {Template}`) but the EmailFolder itself is **not** a node.611 Folder-level access permissions are a Profile / PermissionSet612 concern outside v1.3.613- **Process Builder.** Process Builder is metadata-shaped as a614 Flow (`<processType>Workflow</processType>` in the Flow XML).615 v0.1's Flow extractor already covers it; v1.3 does **not**616 duplicate that coverage. If the admin asks "is this a Process617 Builder or a Workflow Rule?", point to the `Flow:` vs618 `WorkflowRule:` id prefix; the type is the answer.619- **Inline criteria-field `references`.** WorkflowRule,620 AssignmentRule, EscalationRule, and AutoResponseRule ship621 `<criteriaItems>` with `<field>` references. Where the COUNT lands622 differs per family, and getting this wrong reads as "no criteria":623 - **WorkflowRule** — `criteriaItemCount` is a NODE property624 (`properties.criteriaItemCount`), alongside `formula` and625 `booleanFilter`.626 - **AssignmentRule** — per-`ruleEntry`, on the EDGE: each outgoing627 `references` edge carries `entryIndex`, `criteriaItemCount`,628 `hasFormula`. There is no node-level count.629 - **AutoResponseRule** — per-`ruleEntry`, on the outgoing630 `sendsEmail` EDGE (`entryIndex`, `criteriaItemCount`,631 `hasFormula`). There is no node-level count.632 - **EscalationRule** — **no `criteriaItemCount` at all**, on the633 node or on any edge. Its node properties are `active`,634 `ruleEntryCount`, `actionCount`, `conditions`. Say the count is635 unavailable for escalation rules; do not report a missing count636 as zero criteria.637638 None of the four emit per-field `references` edges from the rule to639 each criterion field, and none store the raw criteria field-and-value640 text as a node property — read the `conditions` mirror /641 `ConditionalContext` nodes for the expression instead.642- **Workflow flow-trigger Apex (`callsApex` floor).** When a643 WorkflowRule action is of type `Apex`, the rule node has a644 `callsApex` edge to the named `ApexClass`. The v0.1645 Apex-source extractor handles the receiving class; v1.3 just646 extends the producer side.647- **Migration to Flow.** Workflow Rules and Process Builder648 retirement is in flight (Workflow 2024+, Process Builder649 already retired). v1.3 extracts both because real orgs ship650 them; the skill's migration disclosure recommends Flow as the651 modern replacement but does **not** auto-migrate.652653Treat **dangling edges** as a flag for manual investigation —654they're evidence that a target was deleted, renamed, or never655extracted, and the admin needs the pointer to act on it. Treat656**inactive rules** as still-part-of-the-metadata-surface — they657don't fire, but they're a migration candidate or a cleanup658candidate. Treat **criteria text** as documentation, never as659an evaluator — v1.3 reads it verbatim and stops.660661## Anti-patterns662663| Mistake | Why it's wrong |664|---|---|665| Confusing WorkflowRule with Flow. | WorkflowRule and Flow are different metadata types — `WorkflowRule:Account.X` vs `Flow:Account_X`. The skill's audience is admins working on the legacy declarative tier; if the user names a Flow, defer to the Flow-handling skill (or route to a Flow-narration refusal). Never label a `WorkflowRule:` id as a "flow" in plain English. |666| Not citing the `active` / `isActive` / `ruleStatus` property. | An inactive WorkflowRule is extracted but not firing. The admin's first question after "what does this do" is "is this still on?" — the active flag is load-bearing. Cite it for every rule, every time. |667| Treating extracted criteria as evaluated. | v1.3 reads `<criteriaItems>` and `<formula>` as **text**, not as a predicate. Saying "this rule fires when X = 'Tier 1'" is fine; saying "this rule will fire for *this* Account because its Type is 'Tier 1'" is fabricating a record-level evaluation. Surface the criteria verbatim, name what would have to be true for it to fire, and refuse the per-record half. |668| Silently dropping a dangling action target. | The Q35 honesty anchor verbatim. A WorkflowRule whose fieldUpdate names a `CustomField` not in the extracted set is *evidence* that the field has been removed or renamed. Surface the dangling edge explicitly; never collapse it into the report's empty space. |669| Confusing DuplicateRule with MatchingRule. | DuplicateRule is the policy ("block save on insert when X looks like Y"); MatchingRule is the recipe ("compare fields A, B with methods M"). They're separate types connected by a `references` edge from duplicate to matcher. Walking the chain is the answer to "what fields does this duplicate rule check?"; collapsing the two loses the comparison logic. |670| Skipping the ApprovalProcess approver chain. | ApprovalProcess has `<approvalSteps>[*].<assignedApprover>` — that chain *is* the answer to "who approves this?". Walking the chain step-by-step and naming each approver (Role / Group / User / Queue) is the legible trace the admin came for. Naming only the process and stopping at "an approval process" misses the entire point. |671| Translating EmailTemplate's canonical id with an object prefix. | EmailTemplate is the outlier — `EmailTemplate:{Folder}.{Template}`, NOT `EmailTemplate:{Object}.{Template}`. The template's folder is the parent, not the object the template *concerns*. The body merges field references for whatever object the calling rule operates on. |672| Walking past Letterhead. | Letterhead is a leaf — it has zero outgoing edges. When the admin asks "what does this letterhead do?", surface its properties (`topLineColor`, `bodyColor`, `available`, `description`) and the EmailTemplates that `reference` it (via `sfi.get_edges` with `direction: 'in'`). Don't go further; there's nothing to walk to. |673| Treating the metadata definition as runtime behavior. | "v1.3 lists what's configured" is the constitutional axis. If the admin asks "is this workflow firing right now?", the honest answer is "I can tell you what it's configured to do; whether it fires depends on record-level data v1.x doesn't have." Refuse the runtime half and surface the configuration half. |674| Recommending Flow migration without the impact chain first. | The migration disclosure is a *footer*, not a replacement for the impact analysis. An admin planning a migration without the full target list (action references, sendsEmail targets, callsApex targets) will produce an incomplete Flow. Walk the chain, then recommend migration. |675| Conflating "active rule" with "still relevant" or "live." | An active rule is firing; that's a metadata fact. Whether it's still *correct* — whether the criteria still match the org's business logic — is judgment, not metadata. Don't paper over the gap. |676| Citing `parsed` or `heuristic` confidence on a v1.3 edge. | v1.3's new emission set is **all `declared`**: `sendsEmail`, the reused `triggersOn` / `references` / `callsApex` / `parentOf` instances. The extractor reads directly from XML elements. Don't introduce uncertainty where the metadata is plain. |677| Skipping the boundary disclosure on a clean impact report. | The disclosure protects against the wrong mental model. A clean report that lists every action target still has to coexist with un-modeled gaps (OutboundMessage, WorkflowTask, OrgWideEmailAddress, EmailTemplate merge fields, record-level evaluation). Always disclose. |678679## Example interactions680681### Happy path — "what workflow rules touch Account?"682683See *Reporting format* above for the full transcript. Summary:684parse to `(type: 'WorkflowRule', parentId: 'CustomObject:Account')`,685enumerate via `sfi.list_components`, walk each rule's impact via686`sfi.get_impact`, present as `(active flag → triggers on →687criteria text → actions → sendsEmail targets)`, append the688fixed-form boundary disclosure with the migration paragraph.689690### Cross-reference — "which approval processes use this email template?"691692User: *"Which rules send the `EmailTemplate:Finance.CreditApprovalNeeded`693email template?"*694695Claude's flow:6966971. **Parse** → the question is a `sendsEmail` inversion. Target id698 is `EmailTemplate:Finance.CreditApprovalNeeded` (`Folder.Template`699 format).7002. **Call** `sfi.get_edges` with `{ nodeId:701 'EmailTemplate:Finance.CreditApprovalNeeded', direction: 'in',702 edgeType: 'sendsEmail' }`.7033. **Receive** (illustrative):704705```json706{707 "data": {708 "edges": [709 {710 "fromId": "ApprovalProcess:Account.Credit_Review",711 "toId": "EmailTemplate:Finance.CreditApprovalNeeded",712 "edgeType": "sendsEmail",713 "confidence": "declared",714 "properties": {715 "actionContext": "approvalSteps[1].notificationTemplate",716 "stepLabel": "Finance VP Approval"717 }718 }719 ]720 }721}722```7237244. **Respond:**725726> One rule sends `EmailTemplate:Finance.CreditApprovalNeeded`:727>728> - **`ApprovalProcess:Account.Credit_Review`** (`sendsEmail`,729> `declared`) — sent from approval step `step_2_finance_vp`730> (`approvalSteps[1].notificationTemplate`, label "Finance VP731> Approval"). The step's approver is `Role:Finance_VP` with732> `Queue:Credit_Review_Queue` as an alternate approver.733>734> The process's other steps (`step_1_sales_manager`) and its735> top-level `<emailTemplate>` element point at different736> templates; this query was scoped to737> `EmailTemplate:Finance.CreditApprovalNeeded` only. If you want738> the full template inventory for this process, ask "which email739> templates does `ApprovalProcess:Account.Credit_Review` use?" and740> I'll walk the outgoing edges.741>742> v1.3 legacy-automation coverage: [fixed-form boundary743> disclosure follows].744745### Migration recommendation — "should I migrate this workflow to Flow?"746747User: *"Should I migrate `WorkflowRule:Account.Notify_Sales_On_New_Tier1`748to Flow?"*749750Claude's flow:7517521. **Walk the impact chain first** via `sfi.get_impact` on the753 rule's id. Surface the full target list (the `triggersOn`754 parent object, the `references` edges to the action targets,755 the `sendsEmail` edge to the template, any `callsApex` edges).7562. **Then** add the migration paragraph.7573. **Respond:**758759> Impact chain for `WorkflowRule:Account.Notify_Sales_On_New_Tier1`760> (`active: true`, `triggerType: onCreateOnly`):761>762> - **Triggers on** `CustomObject:Account` (`triggersOn`,763> `declared`).764> - **Criteria** (verbatim): `Account.Type equals 'Tier 1'`.765> - **Actions:**766> - Alert action `Email_Sales_Team` (`references`, `declared`)767> → `sendsEmail` to `EmailTemplate:Sales.WelcomeEmail`768> (`declared`). Recipient: `Group:Sales_Group`. Sender:769> `CurrentUser`.770> - FieldUpdate action `Set_Region_From_Country` (`references`,771> `declared`) — sets `CustomField:Account.Region__c` via the772> formula `IF(BillingCountry = "US", "Americas", "EMEA")`.773>774> **Migration considerations:**775>776> Salesforce has superseded WorkflowRule and Process Builder777> with Flow for most use cases (Workflow Rules retire 2024+,778> Process Builder retired). v1.3 still extracts WorkflowRules779> honestly because real orgs still ship them in production —780> but when planning changes, consider whether the rule should781> move to Flow before extending it. Migration considerations782> grounded in the impact chain above:783>784> - The criteria predicate `Account.Type equals 'Tier 1'`785> translates to a Record-Triggered Flow with a start786> condition `$Record.Type = 'Tier 1'` and the `On Create`787> trigger type matching `onCreateOnly`.788> - The Alert action's `EmailTemplate:Sales.WelcomeEmail`789> target must be reproduced in the Flow via a **790791…(truncated)