Docs / Runbook. Inventory Harvester
Operational runbook

Inventory Harvester

Owner: Org Chat / Introspection.
Apex: FMOrgInventoryHarvester (Queueable + AllowsCallouts), FMOrgInventoryScheduler (Schedulable).
Object: FM_Org_Inventory_Snapshot__c.
Cache: local.FMLLMCache (single-flight key invharvinflight, 900s TTL).
Cron: FM Org Inventory Nightly default 0 0 2 * * ? * (02:00 UTC daily).
MDT: FM_Config.orgChatInventoryCron.

What it does

Harvests org-wide component inventory into FM_Org_Inventory_Snapshot__c so Org Chat's inventory_search tool can answer "what triggers fire on Lead?" without re-querying Tooling per turn.

One execute walks 5 component types sequentially in a single transaction (refactored from chained Queueables; the original chain hit the 5-deep depth cap on PermissionSet):

  1. ApexClass. Tooling REST
  2. ApexTrigger. Tooling REST (captures TableEnumOrId)
  3. FlowDefinitionView. Standard SOQL under SYSTEM_MODE (NOT a Tooling sObject)
  4. ValidationRule. Tooling REST
  5. PermissionSet. Standard SOQL under SYSTEM_MODE

Healthy steady state

System.debug([
  SELECT Component_Type__c, COUNT(Id) c
  FROM FM_Org_Inventory_Snapshot__c
  GROUP BY Component_Type__c
]);

Reference baseline (single sandbox, post 2026-04-27 enable): 570 ApexClass, 65 PermissionSet, 1 FlowDefinitionView. Triggers + validation rules counts are org-shape dependent.

List<CronTrigger> ct = [
  SELECT Id, NextFireTime, State, CronExpression
  FROM CronTrigger
  WHERE CronJobDetail.Name = 'FM Org Inventory Nightly'
];
System.debug(ct);

Expect exactly 1 row, State = WAITING, NextFireTime within 24 h.

Failure modes

F1. "sObject type 'FlowDefinitionView' is not supported"

Cause. Tooling REST doesn't expose FlowDefinitionView or PermissionSet. Both must go through standard Database.query under SYSTEM_MODE.

Fix. Already encoded in harvestType. If this surfaces, someone reverted the routing.

F2. System.AsyncException: Maximum stack depth has been reached

Cause. Old chained-Queueable harvester re-introduced. Chain depth cap is 5 in dev orgs, 100 in prod; chaining one Queueable per type hits the dev cap on type 5.

Fix. Single execute() walks all types in one transaction. Total budget is well under per-transaction cap (≤20 callouts, ≤2000 rows upserted).

F3. Schedulable blocks redeploy of FMOrgInventoryHarvester

Symptom. Deploy fails with "Cannot delete this class because it is referenced elsewhere."

Recovery:

// 1. Abort the cron
for (CronTrigger ct : [
  SELECT Id FROM CronTrigger
  WHERE CronJobDetail.Name = 'FM Org Inventory Nightly'
]) {
  System.abortJob(ct.Id);
}

// 2. Redeploy the class
//    sf project deploy start --target-org Flowmason \
//      --source-dir force-app/main/default/classes/FMOrgInventoryHarvester.cls

// 3. Re-schedule
FMOrgInventoryScheduler.ensureScheduled();

F4. Inventory stale / partial run

Force a fresh harvest: FMOrgInventoryHarvester.enqueue();. Single-flight cache prevents duplicate enqueue if one is already running.

F5. Single-flight cache wedge

Symptom. enqueue() returns the same job id repeatedly even after that job finished.

Cause. finalize() threw before clearing invharvinflight. TTL is 900 s; wait it out, or evict manually:

Cache.OrgPartition p = Cache.Org.getPartition('local.FMLLMCache');
p.remove('invharvinflight');

Manual operations

Force a harvest now

String jobId = FMOrgInventoryHarvester.enqueue();
System.debug('Harvest job: ' + jobId);

Re-schedule with a new cron

Update FM_Config.orgChatInventoryCron, abort the existing CronTrigger, then call FMOrgInventoryScheduler.ensureScheduled().

Disable harvest entirely

No MDT switch. Disablement is operational. Abort the cron. The inventory_search tool degrades to "inventory not yet harvested" when the table is empty.

Purge inventory + re-harvest from scratch

Database.delete([SELECT Id FROM FM_Org_Inventory_Snapshot__c LIMIT 10000], false);
FMOrgInventoryHarvester.enqueue();

Telemetry

Every run publishes one of:

  • org_inventory_harvest_completed. Detail__c JSON: {"runId":"r…","rows":{"ApexClass":570,…}}
  • org_inventory_harvest_failed. Per-type error. {"runId":"r…","type":"FlowDefinitionView","error":"…"}

Subscribe via FlowMasonRun__eFlowMasonRunSubscriberFM_Run_Audit__c. See fmTelemetryDashboard for the rollup.

Related