ReferenceSalesforceL2ADecision Codes

L2A Decision Code Reference

Every code emitted by g-gremlin salesforce l2a, with meaning and apply behavior. Eighteen decision codes, three skip codes, ten apply result codes, five safe actions, and the explicit non-goals that keep this a governed matcher instead of a routing platform.

Published April 18, 2026 • The Salesforce L2A module ships in the Gremlin CLI private beta

Taxonomy at a glance

Scout conclusions, skip reasons, and apply outcomes are intentionally separated.

  • decision_code — what scout concluded (18 codes: 5 safe, 7 review, 6 blocked)
  • skip_code — why evaluation was intentionally skipped (3 codes)
  • apply_result_code — what happened when apply ran (10 codes)
  • safe actions — the only mutations the bounded write surface permits (5 actions)

Safe-to-apply decision codes (5)

Deterministic, single-account outcomes eligible for the bounded write surface.

CodeMeaningBehavior
L2A_SAFE_KNOWN_ACCOUNT_MAP_SINGLE_ACCOUNTLead email domain matches an entry in known_account_map and that entry resolves to exactly one active account.Eligible for lead.set_gremlin_match under gremlin_owned or lead.set_shared_match_if_empty under approved shared_existing.
L2A_SAFE_EXACT_DOMAIN_SINGLE_ACCOUNTNormalized lead email domain maps to exactly one active account; no parent/child collision.Eligible for the bounded write surface. Non-personal email domains only.
L2A_SAFE_EXACT_DOMAIN_PLUS_NAME_SINGLE_ACCOUNTNormalized domain and normalized company name both resolve to the same single account, reinforcing the match.Eligible for the bounded write surface. Highest deterministic band below known_account_map overrides.
L2A_SAFE_EXACT_NON_PERSONAL_EMAIL_DOMAIN_SINGLE_ACCOUNTEmail domain is verified non-personal (not gmail, yahoo, outlook, etc.) and resolves to a single active account.Eligible for the bounded write surface. Consumer email domains never land here.
L2A_SAFE_APPROVED_SHARED_FIELD_EMPTYUnder shared_existing, the approved customer field is empty and the deterministic evidence is strong enough to populate it.Eligible for lead.set_shared_match_if_empty only. Requires acknowledged risk audit hash at apply time.

Review-only decision codes (7)

Ambiguity that an operator resolves, not a heuristic. Routed into grouped_exceptions/.

CodeMeaningBehavior
L2A_REVIEW_DOMAIN_MULTI_ACCOUNTNormalized email domain matches two or more active accounts with comparable evidence.Routed into grouped_exceptions/domain_multi_account/. Operator picks or encodes an override in known_account_map.
L2A_REVIEW_NAME_MULTI_ACCOUNTNormalized company name matches multiple candidate accounts; domain signal is insufficient to break the tie.Routed to review. Never auto-applied.
L2A_REVIEW_PARENT_CHILD_COLLISIONTwo or more candidate accounts in the same hierarchy match the lead with comparable evidence.Routed into grouped_exceptions/parent_child/. Encode the owning level in known_account_map.
L2A_REVIEW_EXISTING_NON_GREMLIN_VALUEUnder observe_existing or shared_existing, the incumbent field already holds a non-Gremlin value that disagrees with the deterministic decision.Never overwrites. Surfaces the disagreement in disagreements.csv.
L2A_REVIEW_CONSUMER_EMAIL_ONLYOnly signal available is a consumer email domain (gmail, yahoo, outlook, etc.). Governed matcher never auto-matches consumer domains.Routed to review. Known_account_map override is the only safe-match path.
L2A_REVIEW_INSUFFICIENT_SIGNALLead has no domain signal, no company-name signal strong enough to match, and no known_account_map hit.Routed to review. Typical for incomplete or early-stage leads.
L2A_REVIEW_ACCOUNT_GRAPH_AMBIGUOUSCandidate accounts form an ambiguous graph — for example, multiple sibling accounts under a parent with no clear owning node.Routed to review. Requires explicit policy before known_account_map is safe.

Blocked decision codes (6)

Hard stops: manual hold, mode unresolved, risk unsigned, field not writable, downstream risk, or target account missing.

CodeMeaningBehavior
L2A_BLOCK_MANUAL_HOLDGremlin_Match_Status__c is set to manual_hold. Operator or script has explicitly paused re-evaluation for this lead.Row is skipped on every run until the hold is cleared.
L2A_BLOCK_MODE_UNRESOLVEDMode could not be resolved from CLI flag, environment variable, gremlin.l2a.yaml, or default. Apply refuses to run.Re-run init or pass --mode explicitly. Apply will not proceed without a resolved mode.
L2A_BLOCK_SHARED_FIELD_NONEMPTYUnder shared_existing with overwrite_mode=if_empty_only, the approved customer field already holds a value.Never overwrites. Clear the field manually or switch to gremlin_owned to proceed.
L2A_BLOCK_FIELD_NOT_WRITABLEThe configured write field is not writable under the current permission context (FLS, profile, or rule restriction).Fix permissions or FLS. Typically the Gremlin permission set covers this when the metadata pack is installed.
L2A_BLOCK_DOWNSTREAM_AUTOMATION_RISKThe configured shared field has unacknowledged downstream automation risk. The risk audit hash is missing from the apply command line.Re-run init to refresh risk_audit.json and pass --ack-risk-audit on apply.
L2A_BLOCK_TARGET_ACCOUNT_MISSINGCandidate account exists in the plan but is missing, inactive, or inaccessible at apply time.Re-run scout to refresh the candidate set. Inactive accounts drop out of eligibility.

Skip codes (3)

Why evaluation was intentionally skipped. Not a verdict.

CodeMeaningBehavior
SKIP_NO_RELEVANT_CHANGEInput fingerprint (lead inputs + matcher version + config version + known_account_map version + account corpus watermark) matches the previous evaluation. Nothing actionable has changed.Row is skipped to avoid flapping. Pass --full-refresh to bypass the fingerprint cache.
SKIP_RECENTLY_EVALUATEDGremlin_Last_Evaluated_At__c is within the configured re-evaluation window.Row is skipped until the window elapses.
SKIP_OUTSIDE_SCOUT_FILTERLead falls outside the --lead-filter expression or the configured scout scope.Row is excluded from the current run; widen --lead-filter to include.

Apply result codes (10)

What happened when apply ran. Separate from decision code for a clean audit trail.

CodeMeaningBehavior
APPLY_DRY_RUN_OKDry-run succeeded: plan revalidated, preconditions hold, write was not executed.Emitted for every row in a dry-run. Receipts are still written.
APPLY_SUCCESS_SET_GREMLIN_MATCHLive apply succeeded in gremlin_owned mode. Gremlin_Matched_Account__c updated.Receipt captures before/after and the plan hash.
APPLY_SUCCESS_SET_SHARED_MATCHLive apply succeeded in shared_existing mode. Approved customer field populated under if_empty_only.Requires both --confirm-plan-hash and --ack-risk-audit.
APPLY_SUCCESS_STATE_ONLYLive apply updated only Gremlin state fields (status, decision code, review flag) without touching a matched-account lookup.Typical for review-only or blocked decision codes promoted into state tracking.
APPLY_SKIPPED_PRECONDITION_CHANGEDLead or candidate account changed between scout and apply. Revalidation detected drift.Row skipped. Re-run scout to refresh the plan.
APPLY_SKIPPED_FIELD_NOW_NONEMPTYUnder shared_existing if_empty_only, the target field became non-empty between scout and apply.Row skipped. No overwrite.
APPLY_FAILED_PLAN_HASH_MISMATCHThe plan hash passed via --confirm-plan-hash does not match the plan at apply time.Apply refuses to run. Regenerate the plan or pass the correct hash.
APPLY_FAILED_PERMISSION_DENIEDSalesforce rejected the write due to profile, permission set, or FLS restriction.Install or update the Gremlin permission set; retry.
APPLY_FAILED_VALIDATION_RULEA Salesforce validation rule blocked the write. The write field or a dependent field failed validation.Adjust the validation rule to accept the Gremlin write pattern, or mark the row manual_hold.
APPLY_FAILED_WRITE_REJECTEDGeneric Salesforce write rejection — duplicate rule, lock contention, or transient failure.Retry with exponential backoff. Persistent failures should be traced individually.

The 5 safe actions

The only mutations the bounded write surface permits. Everything else is an explicit non-goal.

lead.set_gremlin_match

Set Gremlin_Matched_Account__c when the decision is safe and the field is empty or Gremlin-owned.

lead.set_gremlin_state

Set Gremlin_Match_Status__c, Gremlin_Decision_Code__c, and Gremlin_Review_Required__c.

lead.write_gremlin_provenance

Set Gremlin_Last_Evaluated_At__c, Gremlin_Last_Input_Fingerprint__c, Gremlin_Receipt_Id__c, and Gremlin_Apply_Result_Code__c.

lead.set_shared_match_if_empty

In approved shared_existing mode, write the approved customer field when it is empty and the decision is safe.

lead.mark_manual_hold

Mark a lead as manual_hold when Gremlin detects manual clear, manual set, or an operator hold condition.

Evaluation fingerprint

The deterministic input set that drives SKIP_NO_RELEVANT_CHANGE. If any component changes, the lead is re-evaluated.

  • Normalized lead match inputs (email, domain, company name, website)
  • Matcher version
  • Config version (gremlin.l2a.yaml hash)
  • known_account_map version
  • Account corpus watermark or scout snapshot version

Explicit non-goals

The boundaries that keep this a governed matcher instead of a routing platform.

  • No OwnerId writes, no territory writes
  • No lead status mutation, no lead conversion
  • No Account, Contact, Opportunity, or Case writes
  • No account create, merge, or delete
  • No Apex deployment, no Flow deployment
  • No auto-deploy of AI-generated metadata
  • No overwrite of non-empty customer-owned shared fields by default
  • No silent auto-resolution of ambiguity with fuzzy or AI reasoning
  • No promise of real-time in-org trigger behavior — scheduled continuous evaluation only

FAQ

How many L2A decision codes are there?

Eighteen decision codes in the core taxonomy: 5 safe-to-apply, 7 review-only, and 6 blocked. Three skip codes sit alongside (for why evaluation was intentionally skipped), and 10 apply result codes describe what happened when apply ran. Scout conclusions, skip reasons, and apply outcomes are intentionally separated.

Why are skip codes separate from decision codes?

Because SKIP_NO_RELEVANT_CHANGE, SKIP_RECENTLY_EVALUATED, and SKIP_OUTSIDE_SCOUT_FILTER describe why evaluation was intentionally not run — not a verdict about match quality. Mixing them into the decision taxonomy would pollute match analytics and confuse operator dashboards.

Why are apply result codes separate from decision codes?

Because plan-hash mismatch, stale-row failures, permission denials, and validation rule rejections are apply-time mechanics, not match conclusions. Keeping them separate keeps the decision taxonomy deterministic and lets operators audit the decision chain without apply-time noise.

Can Gremlin add custom decision codes for my org?

Not in v1. The code taxonomy is closed — extending it would break the determinism guarantee. Org-specific overrides belong in known_account_map (exact domains and explicitly approved suffix domains) rather than in new decision codes.

How do I look up the decision code for a specific lead?

Run g-gremlin salesforce l2a trace --email <email> or --lead-id <id>. The trace output includes the decision code, evidence chain, candidate accounts considered, and any existing-field comparison. It is the authoritative per-lead explanation.

Keep the conversation going

These pages are meant to help operators solve real problems. If you want the next guide, grab the low-friction option. If you need the implementation, not just the guide, book time.

Stay in the loop

Get the next guide when it ships

I publish architecture guides grounded in real implementations. No generic AI filler.

Use your work email so I can keep the list useful and relevant.

Book Mike directly

Need the implementation, not just the guide?

Book a 15-minute working session with Mike right on his calendar. Tooling, consulting, or a mix of both is fine.

Open Mike's calendar

If you want me to come in with context, leave your email and a short note before the call.

I'll route new requests into the internal website inquiries inbox so I can follow up fast.