sf-intelligence 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +927 -182
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/index.js
CHANGED
|
@@ -357,12 +357,163 @@ var init_src = __esm({
|
|
|
357
357
|
}
|
|
358
358
|
});
|
|
359
359
|
|
|
360
|
+
// ../graph/dist/src/relationship-refs.js
|
|
361
|
+
var objectApiNameOf, AMBIGUOUS, buildRelationshipMaps, resolveTraversalTarget, RELATIONSHIP_RESOLVER_SOURCE, mintRelationshipTraversalEdges;
|
|
362
|
+
var init_relationship_refs = __esm({
|
|
363
|
+
"../graph/dist/src/relationship-refs.js"() {
|
|
364
|
+
"use strict";
|
|
365
|
+
objectApiNameOf = (parentId) => {
|
|
366
|
+
if (parentId === null)
|
|
367
|
+
return null;
|
|
368
|
+
const prefix = "CustomObject:";
|
|
369
|
+
return parentId.startsWith(prefix) ? parentId.slice(prefix.length) : null;
|
|
370
|
+
};
|
|
371
|
+
AMBIGUOUS = /* @__PURE__ */ Symbol("ambiguous");
|
|
372
|
+
buildRelationshipMaps = (nodes) => {
|
|
373
|
+
const parentward = /* @__PURE__ */ new Map();
|
|
374
|
+
const childward = /* @__PURE__ */ new Map();
|
|
375
|
+
for (const node of nodes) {
|
|
376
|
+
if (node.type !== "CustomField")
|
|
377
|
+
continue;
|
|
378
|
+
const owningObject = objectApiNameOf(node.parentId);
|
|
379
|
+
if (owningObject === null)
|
|
380
|
+
continue;
|
|
381
|
+
const referenceTo = node.properties["referenceTo"];
|
|
382
|
+
if (typeof referenceTo !== "string" || referenceTo.length === 0)
|
|
383
|
+
continue;
|
|
384
|
+
const fieldApiName = node.apiName;
|
|
385
|
+
const traversal = fieldApiName.endsWith("__c") ? `${fieldApiName.slice(0, -3)}__r` : fieldApiName.replace(/Id$/, "");
|
|
386
|
+
if (traversal.length > 0) {
|
|
387
|
+
const key = `${owningObject.toLowerCase()}|${traversal.toLowerCase()}`;
|
|
388
|
+
const existing = parentward.get(key);
|
|
389
|
+
if (existing === void 0) {
|
|
390
|
+
parentward.set(key, referenceTo);
|
|
391
|
+
} else if (existing !== referenceTo) {
|
|
392
|
+
parentward.set(key, AMBIGUOUS);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const relationshipName = node.properties["relationshipName"];
|
|
396
|
+
if (typeof relationshipName === "string" && relationshipName.length > 0) {
|
|
397
|
+
for (const key of [relationshipName, `${relationshipName}__r`]) {
|
|
398
|
+
const lower = key.toLowerCase();
|
|
399
|
+
const existing = childward.get(lower);
|
|
400
|
+
if (existing === void 0) {
|
|
401
|
+
childward.set(lower, owningObject);
|
|
402
|
+
} else if (existing !== owningObject) {
|
|
403
|
+
childward.set(lower, AMBIGUOUS);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const resolve18 = (raw) => {
|
|
409
|
+
const out = /* @__PURE__ */ new Map();
|
|
410
|
+
for (const [key, value] of raw) {
|
|
411
|
+
if (value !== AMBIGUOUS)
|
|
412
|
+
out.set(key, value);
|
|
413
|
+
}
|
|
414
|
+
return out;
|
|
415
|
+
};
|
|
416
|
+
return { parentward: resolve18(parentward), childward: resolve18(childward) };
|
|
417
|
+
};
|
|
418
|
+
resolveTraversalTarget = (path2, owningObject, maps) => {
|
|
419
|
+
const segments = path2.split(".");
|
|
420
|
+
if (segments.length < 2)
|
|
421
|
+
return null;
|
|
422
|
+
let current = owningObject;
|
|
423
|
+
for (const hop of segments.slice(0, -1)) {
|
|
424
|
+
const next = maps.parentward.get(`${current.toLowerCase()}|${hop.toLowerCase()}`);
|
|
425
|
+
if (next === void 0)
|
|
426
|
+
return null;
|
|
427
|
+
current = next;
|
|
428
|
+
}
|
|
429
|
+
const field = segments[segments.length - 1];
|
|
430
|
+
if (field === void 0 || field.length === 0)
|
|
431
|
+
return null;
|
|
432
|
+
return `CustomField:${current}.${field}`;
|
|
433
|
+
};
|
|
434
|
+
RELATIONSHIP_RESOLVER_SOURCE = "relationship-resolver";
|
|
435
|
+
mintRelationshipTraversalEdges = (nodes, edges) => {
|
|
436
|
+
const maps = buildRelationshipMaps(nodes);
|
|
437
|
+
const fieldIds = /* @__PURE__ */ new Set();
|
|
438
|
+
for (const node of nodes) {
|
|
439
|
+
if (node.type === "CustomField")
|
|
440
|
+
fieldIds.add(node.id);
|
|
441
|
+
}
|
|
442
|
+
const seen = /* @__PURE__ */ new Set();
|
|
443
|
+
for (const edge of edges) {
|
|
444
|
+
seen.add(`${edge.fromId}\0${edge.toId}\0${edge.edgeType}`);
|
|
445
|
+
}
|
|
446
|
+
const emit = (fromId, toId, confidence, properties) => {
|
|
447
|
+
if (!fieldIds.has(toId))
|
|
448
|
+
return;
|
|
449
|
+
const key = `${fromId}\0${toId}\0references`;
|
|
450
|
+
if (seen.has(key))
|
|
451
|
+
return;
|
|
452
|
+
seen.add(key);
|
|
453
|
+
edges.push({
|
|
454
|
+
fromId,
|
|
455
|
+
toId,
|
|
456
|
+
edgeType: "references",
|
|
457
|
+
confidence,
|
|
458
|
+
source: RELATIONSHIP_RESOLVER_SOURCE,
|
|
459
|
+
properties
|
|
460
|
+
});
|
|
461
|
+
};
|
|
462
|
+
for (const node of nodes) {
|
|
463
|
+
if (node.type === "CustomField") {
|
|
464
|
+
const refs = node.properties["formulaRelationshipRefs"];
|
|
465
|
+
const owningObject = objectApiNameOf(node.parentId);
|
|
466
|
+
if (Array.isArray(refs) && owningObject !== null) {
|
|
467
|
+
for (const ref of refs) {
|
|
468
|
+
if (typeof ref !== "string")
|
|
469
|
+
continue;
|
|
470
|
+
const toId = resolveTraversalTarget(ref, owningObject, maps);
|
|
471
|
+
if (toId === null)
|
|
472
|
+
continue;
|
|
473
|
+
emit(node.id, toId, "parsed", {
|
|
474
|
+
referenceKind: "formulaRelationshipTraversal",
|
|
475
|
+
traversalPath: ref
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (node.type === "FlexiPage") {
|
|
481
|
+
const related = node.properties["relatedListFieldRefs"];
|
|
482
|
+
if (!Array.isArray(related))
|
|
483
|
+
continue;
|
|
484
|
+
for (const entry of related) {
|
|
485
|
+
if (typeof entry !== "object" || entry === null)
|
|
486
|
+
continue;
|
|
487
|
+
const { relatedListApiName, fields } = entry;
|
|
488
|
+
if (typeof relatedListApiName !== "string")
|
|
489
|
+
continue;
|
|
490
|
+
const childObject = maps.childward.get(relatedListApiName.toLowerCase());
|
|
491
|
+
if (childObject === void 0 || !Array.isArray(fields))
|
|
492
|
+
continue;
|
|
493
|
+
for (const field of fields) {
|
|
494
|
+
if (typeof field !== "string" || field.length === 0)
|
|
495
|
+
continue;
|
|
496
|
+
if (field.includes("."))
|
|
497
|
+
continue;
|
|
498
|
+
emit(node.id, `CustomField:${childObject}.${field}`, "parsed", {
|
|
499
|
+
referenceKind: "relatedListFieldAlias",
|
|
500
|
+
relatedListApiName
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
360
510
|
// ../graph/dist/src/import.js
|
|
361
511
|
var IMPORT_BATCH_SIZE, INSERT_NODE_SQL, INSERT_EDGE_SQL, NODE_COLUMN_COUNT, EDGE_COLUMN_COUNT, buildMultiRowUpsertSql, canonicalJson, relativizeSourcePath, nodeRowParams, edgeRowParams, insertNode, insertEdge, commitBatched, canonicalizeApexCallEdgeTargets, canonicalizeEdgeTargetsByCase, CUSTOM_FIELD_PREFIX, ACTIVITY_OBJECT_LOWER, ACTIVITY_CHILD_OBJECTS_LOWER, splitFieldId, canonicalizeActivityPolymorphicFieldEdgeTargets, ACTIVITY_POLYMORPHIC_SLOTS, POLYMORPHIC_MIRROR_EDGE_TYPES, ACTIVITY_POLYMORPHIC_SOURCE, mintPolymorphicActivityFieldEdges, canonicalizeFieldEdgeTargets, canonicalizeObjectEdgeTargets, canonicalizeLabelEdgeTargets, canonicalizeResourceEdgeTargets, mintFutureDispatchEdges, importExtractionResults;
|
|
362
512
|
var init_import = __esm({
|
|
363
513
|
"../graph/dist/src/import.js"() {
|
|
364
514
|
"use strict";
|
|
365
515
|
init_src();
|
|
516
|
+
init_relationship_refs();
|
|
366
517
|
IMPORT_BATCH_SIZE = 500;
|
|
367
518
|
INSERT_NODE_SQL = `INSERT OR REPLACE INTO nodes (
|
|
368
519
|
id, type, api_name, label, parent_id, source_path,
|
|
@@ -738,6 +889,7 @@ var init_import = __esm({
|
|
|
738
889
|
canonicalizeLabelEdgeTargets(allNodes, allEdges);
|
|
739
890
|
canonicalizeResourceEdgeTargets(allNodes, allEdges);
|
|
740
891
|
mintFutureDispatchEdges(allNodes, allEdges);
|
|
892
|
+
mintRelationshipTraversalEdges(allNodes, allEdges);
|
|
741
893
|
const nodeOutcome = await commitBatched(connection, allNodes, insertNode);
|
|
742
894
|
if (!nodeOutcome.ok) {
|
|
743
895
|
return err({
|
|
@@ -772,12 +924,19 @@ var init_import = __esm({
|
|
|
772
924
|
});
|
|
773
925
|
|
|
774
926
|
// ../graph/dist/src/apply-change-set.js
|
|
775
|
-
var componentTypeFromNodeId, INCREMENTAL_DELTA_CAP, changeSetSize, DELETE_NODE_SQL, DELETE_EDGE_SQL, PK_SEP, edgePk, rowKey, readNodeRowKeys, readEdgeRowKeys, computeChangeSet, upsertRowsChunked, scalarCount, applyChangeSet, pruneStaleNodes;
|
|
927
|
+
var SYNTHETIC_MINT_SOURCES, componentTypeFromNodeId, INCREMENTAL_DELTA_CAP, changeSetSize, DELETE_NODE_SQL, DELETE_EDGE_SQL, PK_SEP, edgePk, rowKey, readNodeRowKeys, readEdgeRowKeys, computeChangeSet, upsertRowsChunked, scalarCount, applyChangeSet, pruneStaleNodes;
|
|
776
928
|
var init_apply_change_set = __esm({
|
|
777
929
|
"../graph/dist/src/apply-change-set.js"() {
|
|
778
930
|
"use strict";
|
|
779
931
|
init_src();
|
|
780
932
|
init_import();
|
|
933
|
+
init_relationship_refs();
|
|
934
|
+
SYNTHETIC_MINT_SOURCES = /* @__PURE__ */ new Set([
|
|
935
|
+
RELATIONSHIP_RESOLVER_SOURCE,
|
|
936
|
+
// mintFutureDispatchEdges (import.ts) — same hazard, mirrored earlier without
|
|
937
|
+
// the exclusion; correcting both together keeps the two passes consistent.
|
|
938
|
+
"graph-future-dispatch"
|
|
939
|
+
]);
|
|
781
940
|
componentTypeFromNodeId = (id) => {
|
|
782
941
|
const sep6 = id.indexOf(":");
|
|
783
942
|
if (sep6 < 0)
|
|
@@ -854,6 +1013,7 @@ var init_apply_change_set = __esm({
|
|
|
854
1013
|
canonicalizeLabelEdgeTargets([...desiredNodes.values()], desiredEdgeList);
|
|
855
1014
|
canonicalizeResourceEdgeTargets([...desiredNodes.values()], desiredEdgeList);
|
|
856
1015
|
mintFutureDispatchEdges([...desiredNodes.values()], desiredEdgeList);
|
|
1016
|
+
mintRelationshipTraversalEdges([...desiredNodes.values()], desiredEdgeList);
|
|
857
1017
|
const desiredEdges = /* @__PURE__ */ new Map();
|
|
858
1018
|
for (const edge of desiredEdgeList) {
|
|
859
1019
|
const pk = edgePk(edge.fromId, edge.toId, edge.edgeType, edge.source);
|
|
@@ -862,8 +1022,11 @@ var init_apply_change_set = __esm({
|
|
|
862
1022
|
}
|
|
863
1023
|
const finalNodeIds = new Set(desiredNodes.keys());
|
|
864
1024
|
const reRanSources = /* @__PURE__ */ new Set();
|
|
865
|
-
for (const e of desiredEdges.values())
|
|
1025
|
+
for (const e of desiredEdges.values()) {
|
|
1026
|
+
if (SYNTHETIC_MINT_SOURCES.has(e.source))
|
|
1027
|
+
continue;
|
|
866
1028
|
reRanSources.add(e.source);
|
|
1029
|
+
}
|
|
867
1030
|
let currentNodeKeys;
|
|
868
1031
|
let currentEdges;
|
|
869
1032
|
try {
|
|
@@ -3426,6 +3589,7 @@ var init_src2 = __esm({
|
|
|
3426
3589
|
"../graph/dist/src/index.js"() {
|
|
3427
3590
|
"use strict";
|
|
3428
3591
|
init_import();
|
|
3592
|
+
init_relationship_refs();
|
|
3429
3593
|
init_apply_change_set();
|
|
3430
3594
|
init_migrations();
|
|
3431
3595
|
init_queries();
|
|
@@ -8612,6 +8776,27 @@ var init_condition_extractor = __esm({
|
|
|
8612
8776
|
conditionIndex: index
|
|
8613
8777
|
}
|
|
8614
8778
|
};
|
|
8779
|
+
const isWellFormedFieldId = (id) => {
|
|
8780
|
+
const body = id.startsWith("CustomField:") ? id.slice("CustomField:".length) : "";
|
|
8781
|
+
if (body.length === 0 || body.startsWith("$"))
|
|
8782
|
+
return false;
|
|
8783
|
+
const parts = body.split(".");
|
|
8784
|
+
return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;
|
|
8785
|
+
};
|
|
8786
|
+
const fieldEdges = fieldRefs.filter(isWellFormedFieldId).map((toId) => ({
|
|
8787
|
+
fromId: conditionContextId,
|
|
8788
|
+
toId,
|
|
8789
|
+
edgeType: "readsFrom",
|
|
8790
|
+
confidence,
|
|
8791
|
+
source: EXTRACTOR_SOURCE,
|
|
8792
|
+
properties: {
|
|
8793
|
+
kind,
|
|
8794
|
+
conditionIndex: index,
|
|
8795
|
+
// The firer the condition belongs to, so a consumer reading the field's
|
|
8796
|
+
// incoming edges can name the Flow / rule without a second hop.
|
|
8797
|
+
firerId: parentId
|
|
8798
|
+
}
|
|
8799
|
+
}));
|
|
8615
8800
|
const mirror = {
|
|
8616
8801
|
kind,
|
|
8617
8802
|
conditionContextId,
|
|
@@ -8619,13 +8804,14 @@ var init_condition_extractor = __esm({
|
|
|
8619
8804
|
fieldRefs,
|
|
8620
8805
|
...sourceName !== null ? { sourceName } : {}
|
|
8621
8806
|
};
|
|
8622
|
-
return { node, edge, mirror };
|
|
8807
|
+
return { node, edge, fieldEdges, mirror };
|
|
8623
8808
|
};
|
|
8624
8809
|
extractConditions = (options) => {
|
|
8625
8810
|
const { parentId, sources, parentSourcePath, parentApiVersion = null, parentObjectApiName = null, indexOffset = 0 } = options;
|
|
8626
8811
|
const conditionNodes = [];
|
|
8627
8812
|
const firesWhenEdges = [];
|
|
8628
8813
|
const conditionsMirror = [];
|
|
8814
|
+
const conditionFieldEdges = [];
|
|
8629
8815
|
for (let i = 0; i < sources.length; i += 1) {
|
|
8630
8816
|
const source = sources[i];
|
|
8631
8817
|
const index = indexOffset + i;
|
|
@@ -8693,8 +8879,14 @@ var init_condition_extractor = __esm({
|
|
|
8693
8879
|
conditionNodes.push(triple.node);
|
|
8694
8880
|
firesWhenEdges.push(triple.edge);
|
|
8695
8881
|
conditionsMirror.push(triple.mirror);
|
|
8882
|
+
conditionFieldEdges.push(...triple.fieldEdges);
|
|
8696
8883
|
}
|
|
8697
|
-
return {
|
|
8884
|
+
return {
|
|
8885
|
+
conditionNodes,
|
|
8886
|
+
firesWhenEdges,
|
|
8887
|
+
conditionsMirror,
|
|
8888
|
+
conditionFieldEdges
|
|
8889
|
+
};
|
|
8698
8890
|
};
|
|
8699
8891
|
}
|
|
8700
8892
|
});
|
|
@@ -9157,7 +9349,7 @@ var init_workflow_rule = __esm({
|
|
|
9157
9349
|
actions.push(resolved.value);
|
|
9158
9350
|
}
|
|
9159
9351
|
const conditionSources = collectWorkflowRuleConditionSources(rule);
|
|
9160
|
-
const { conditionNodes, firesWhenEdges, conditionsMirror } = extractConditions({
|
|
9352
|
+
const { conditionNodes, firesWhenEdges, conditionsMirror, conditionFieldEdges } = extractConditions({
|
|
9161
9353
|
parentId: ruleId,
|
|
9162
9354
|
sources: conditionSources,
|
|
9163
9355
|
parentSourcePath: path2,
|
|
@@ -9227,7 +9419,7 @@ var init_workflow_rule = __esm({
|
|
|
9227
9419
|
for (const action of actions) {
|
|
9228
9420
|
edges.push(...edgesForAction(action, ruleId, objectApiName, alertTemplateMap, fieldUpdateMap, seen));
|
|
9229
9421
|
}
|
|
9230
|
-
edges.push(...firesWhenEdges);
|
|
9422
|
+
edges.push(...firesWhenEdges, ...conditionFieldEdges);
|
|
9231
9423
|
return ok({ node, edges, conditionNodes });
|
|
9232
9424
|
};
|
|
9233
9425
|
extractWorkflowRule = async (path2) => {
|
|
@@ -9775,7 +9967,7 @@ var init_approval_process = __esm({
|
|
|
9775
9967
|
}
|
|
9776
9968
|
}
|
|
9777
9969
|
}
|
|
9778
|
-
const { conditionNodes, firesWhenEdges, conditionsMirror } = extractConditions({
|
|
9970
|
+
const { conditionNodes, firesWhenEdges, conditionsMirror, conditionFieldEdges } = extractConditions({
|
|
9779
9971
|
parentId: processId,
|
|
9780
9972
|
sources: entryConditionSources,
|
|
9781
9973
|
parentSourcePath: path2,
|
|
@@ -9899,7 +10091,7 @@ var init_approval_process = __esm({
|
|
|
9899
10091
|
properties: { referenceKind: "allowedSubmitter", submitterType }
|
|
9900
10092
|
});
|
|
9901
10093
|
}
|
|
9902
|
-
edges.push(...firesWhenEdges);
|
|
10094
|
+
edges.push(...firesWhenEdges, ...conditionFieldEdges);
|
|
9903
10095
|
return ok({ nodes: [node, ...conditionNodes], edges });
|
|
9904
10096
|
};
|
|
9905
10097
|
}
|
|
@@ -10161,7 +10353,7 @@ var init_assignment_rule = __esm({
|
|
|
10161
10353
|
conditionSources.push(source);
|
|
10162
10354
|
conditionEntryIndices.push(entryIndex);
|
|
10163
10355
|
}
|
|
10164
|
-
const { conditionNodes, firesWhenEdges, conditionsMirror } = extractConditions({
|
|
10356
|
+
const { conditionNodes, firesWhenEdges, conditionsMirror, conditionFieldEdges } = extractConditions({
|
|
10165
10357
|
parentId: ruleId,
|
|
10166
10358
|
sources: conditionSources,
|
|
10167
10359
|
parentSourcePath: path2,
|
|
@@ -10245,7 +10437,7 @@ var init_assignment_rule = __esm({
|
|
|
10245
10437
|
});
|
|
10246
10438
|
}
|
|
10247
10439
|
}
|
|
10248
|
-
edges.push(...firesWhenEdges);
|
|
10440
|
+
edges.push(...firesWhenEdges, ...conditionFieldEdges);
|
|
10249
10441
|
return ok({ node, edges, conditionNodes });
|
|
10250
10442
|
};
|
|
10251
10443
|
extractAssignmentRule = async (path2) => {
|
|
@@ -10906,7 +11098,7 @@ var init_auto_response_rule = __esm({
|
|
|
10906
11098
|
conditionSources.push(entry.conditionSource);
|
|
10907
11099
|
}
|
|
10908
11100
|
}
|
|
10909
|
-
const { conditionNodes, firesWhenEdges, conditionsMirror } = extractConditions({
|
|
11101
|
+
const { conditionNodes, firesWhenEdges, conditionsMirror, conditionFieldEdges } = extractConditions({
|
|
10910
11102
|
parentId: ruleId,
|
|
10911
11103
|
sources: conditionSources,
|
|
10912
11104
|
parentSourcePath: path2,
|
|
@@ -10957,7 +11149,7 @@ var init_auto_response_rule = __esm({
|
|
|
10957
11149
|
}
|
|
10958
11150
|
});
|
|
10959
11151
|
}
|
|
10960
|
-
edges.push(...firesWhenEdges);
|
|
11152
|
+
edges.push(...firesWhenEdges, ...conditionFieldEdges);
|
|
10961
11153
|
return ok({ node, edges, conditionNodes });
|
|
10962
11154
|
};
|
|
10963
11155
|
extractAutoResponseRule = async (path2) => {
|
|
@@ -12676,13 +12868,27 @@ var init_custom_application = __esm({
|
|
|
12676
12868
|
});
|
|
12677
12869
|
|
|
12678
12870
|
// ../extractors/dist/src/formula-references.js
|
|
12679
|
-
var TOKENIZER_SOURCE, PERMISSION_GLOBAL_REFERENCE, buildReferencesEdges;
|
|
12871
|
+
var TOKENIZER_SOURCE, PERMISSION_GLOBAL_REFERENCE, collectRelationshipRefs, buildReferencesEdges;
|
|
12680
12872
|
var init_formula_references = __esm({
|
|
12681
12873
|
"../extractors/dist/src/formula-references.js"() {
|
|
12682
12874
|
"use strict";
|
|
12683
12875
|
init_src5();
|
|
12684
12876
|
TOKENIZER_SOURCE = "formula-tokenizer";
|
|
12685
12877
|
PERMISSION_GLOBAL_REFERENCE = /^\$Permission\.([A-Za-z_][A-Za-z_0-9]*)$/i;
|
|
12878
|
+
collectRelationshipRefs = (formula) => {
|
|
12879
|
+
const tokenized = tokenizeFormula(formula);
|
|
12880
|
+
if (!tokenized.ok)
|
|
12881
|
+
return [];
|
|
12882
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12883
|
+
for (const ref of tokenized.value.references) {
|
|
12884
|
+
if (!ref.path.includes("."))
|
|
12885
|
+
continue;
|
|
12886
|
+
if (ref.path.startsWith("$"))
|
|
12887
|
+
continue;
|
|
12888
|
+
seen.add(ref.path);
|
|
12889
|
+
}
|
|
12890
|
+
return [...seen].sort();
|
|
12891
|
+
};
|
|
12686
12892
|
buildReferencesEdges = (formula, fromId, parentObjectApiName, tokenizedFromField) => {
|
|
12687
12893
|
const tokenized = tokenizeFormula(formula);
|
|
12688
12894
|
if (!tokenized.ok)
|
|
@@ -12742,7 +12948,7 @@ var init_formula_references = __esm({
|
|
|
12742
12948
|
import { readFile as readFile26 } from "node:fs/promises";
|
|
12743
12949
|
import { basename as basename4, dirname as dirname11 } from "node:path";
|
|
12744
12950
|
import { XMLParser as XMLParser17, XMLValidator as XMLValidator17 } from "fast-xml-parser";
|
|
12745
|
-
var FIELD_FILE_SUFFIX, ROOT_ELEMENT16, FIELDS_DIR_NAME, PICKLIST_TYPES, FORMULA_ELEMENT_NAME, STANDARD_FIELD_TYPES, unwrapSingle17, coerceBoolean11, toNullableString, toNullableNumber, toBooleanWithDefault, coerceIsActiveDefaultTrue, readAndValidateXml16, validateRoot12, derivePathParts3, extractPicklistValues, extractControllingFieldInfo, extractValueSetName, SUMMARY_DATA_TYPE, extractSummaryInfo, buildProperties, extractCustomField;
|
|
12951
|
+
var FIELD_FILE_SUFFIX, ROOT_ELEMENT16, FIELDS_DIR_NAME, PICKLIST_TYPES, FORMULA_ELEMENT_NAME, ROLLUP_SUMMARY_SOURCE, STANDARD_FIELD_TYPES, unwrapSingle17, coerceBoolean11, toNullableString, toNullableNumber, toBooleanWithDefault, coerceIsActiveDefaultTrue, readAndValidateXml16, validateRoot12, derivePathParts3, extractPicklistValues, extractControllingFieldInfo, extractValueSetName, SUMMARY_DATA_TYPE, extractSummaryInfo, extractSummaryFilterFields, buildProperties, extractCustomField;
|
|
12746
12952
|
var init_custom_field = __esm({
|
|
12747
12953
|
"../extractors/dist/src/custom-field.js"() {
|
|
12748
12954
|
"use strict";
|
|
@@ -12754,6 +12960,7 @@ var init_custom_field = __esm({
|
|
|
12754
12960
|
FIELDS_DIR_NAME = "fields";
|
|
12755
12961
|
PICKLIST_TYPES = ["Picklist", "MultiselectPicklist"];
|
|
12756
12962
|
FORMULA_ELEMENT_NAME = "formula";
|
|
12963
|
+
ROLLUP_SUMMARY_SOURCE = "rollup-summary";
|
|
12757
12964
|
STANDARD_FIELD_TYPES = {
|
|
12758
12965
|
Email: "Email",
|
|
12759
12966
|
Phone: "Phone",
|
|
@@ -12932,6 +13139,26 @@ var init_custom_field = __esm({
|
|
|
12932
13139
|
summaryOperation: toNullableString(rootObj["summaryOperation"])
|
|
12933
13140
|
};
|
|
12934
13141
|
};
|
|
13142
|
+
extractSummaryFilterFields = (rootObj) => {
|
|
13143
|
+
const raw = rootObj["summaryFilterItems"];
|
|
13144
|
+
if (raw === void 0 || raw === null)
|
|
13145
|
+
return [];
|
|
13146
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
13147
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13148
|
+
const out = [];
|
|
13149
|
+
for (const item of items) {
|
|
13150
|
+
if (typeof item !== "object" || item === null)
|
|
13151
|
+
continue;
|
|
13152
|
+
const field = toNullableString(item["field"]);
|
|
13153
|
+
if (field === null || field.length === 0)
|
|
13154
|
+
continue;
|
|
13155
|
+
if (seen.has(field))
|
|
13156
|
+
continue;
|
|
13157
|
+
seen.add(field);
|
|
13158
|
+
out.push(field);
|
|
13159
|
+
}
|
|
13160
|
+
return out;
|
|
13161
|
+
};
|
|
12935
13162
|
buildProperties = (rootObj, dataType) => {
|
|
12936
13163
|
const isPicklist = PICKLIST_TYPES.includes(dataType);
|
|
12937
13164
|
const formula = toNullableString(rootObj["formula"]);
|
|
@@ -12971,6 +13198,15 @@ var init_custom_field = __esm({
|
|
|
12971
13198
|
// `true` keeps stored fields byte-identical while making formula fields
|
|
12972
13199
|
// self-describing.
|
|
12973
13200
|
...isFormula ? { isFormula: true } : {},
|
|
13201
|
+
// OMIT-when-empty: the cross-object traversals in this formula, verbatim
|
|
13202
|
+
// (`Parent__r.Field__c`). Only the import-time relationship resolver can
|
|
13203
|
+
// turn these into edges — it is the only layer that can see the lookup
|
|
13204
|
+
// fields of every OTHER object. Stored rather than dropped so a field read
|
|
13205
|
+
// exclusively through a traversal stops looking orphaned.
|
|
13206
|
+
...(() => {
|
|
13207
|
+
const relationshipRefs = isFormula ? collectRelationshipRefs(formula ?? "") : [];
|
|
13208
|
+
return relationshipRefs.length > 0 ? { formulaRelationshipRefs: relationshipRefs } : {};
|
|
13209
|
+
})(),
|
|
12974
13210
|
// OMIT-when-null (unlike the fixed keys above): only GlobalValueSet-driven
|
|
12975
13211
|
// picklists carry a value-set name, and a `valueSetName: null` row on every
|
|
12976
13212
|
// CustomField would churn every rendered markdown file in every vault
|
|
@@ -13075,6 +13311,28 @@ var init_custom_field = __esm({
|
|
|
13075
13311
|
source: "custom-field-extractor",
|
|
13076
13312
|
properties: { relationshipType }
|
|
13077
13313
|
}));
|
|
13314
|
+
const summaryInfo = extractSummaryInfo(rootObj, dataType);
|
|
13315
|
+
const rollupTargets = [
|
|
13316
|
+
["summarizedField", summaryInfo.summarizedField],
|
|
13317
|
+
["summaryForeignKey", summaryInfo.summaryForeignKey],
|
|
13318
|
+
// A roll-up's FILTER also names child fields, and the platform refuses to
|
|
13319
|
+
// delete those too — the filter would no longer compile. Same file, same
|
|
13320
|
+
// parent-side invisibility as the two above.
|
|
13321
|
+
...dataType === SUMMARY_DATA_TYPE ? extractSummaryFilterFields(rootObj).map((field) => ["summaryFilterItem", field]) : []
|
|
13322
|
+
];
|
|
13323
|
+
const rollupEdges = rollupTargets.filter((entry) => entry[1] !== null && entry[1].length > 0).map(([rollupRole, target]) => ({
|
|
13324
|
+
fromId: nodeId,
|
|
13325
|
+
toId: `CustomField:${target}`,
|
|
13326
|
+
edgeType: "references",
|
|
13327
|
+
confidence: "declared",
|
|
13328
|
+
source: ROLLUP_SUMMARY_SOURCE,
|
|
13329
|
+
properties: {
|
|
13330
|
+
rollupRole,
|
|
13331
|
+
// OMIT-when-null so a `count` roll-up (no operation recorded) does not
|
|
13332
|
+
// carry a null row into every stored edge.
|
|
13333
|
+
...summaryInfo.summaryOperation !== null ? { summaryOperation: summaryInfo.summaryOperation } : {}
|
|
13334
|
+
}
|
|
13335
|
+
}));
|
|
13078
13336
|
const valueSetName = extractValueSetName(rootObj);
|
|
13079
13337
|
const valueSetEdges = valueSetName !== null ? [
|
|
13080
13338
|
{
|
|
@@ -13088,7 +13346,13 @@ var init_custom_field = __esm({
|
|
|
13088
13346
|
] : [];
|
|
13089
13347
|
return ok({
|
|
13090
13348
|
nodes: [node],
|
|
13091
|
-
edges: [
|
|
13349
|
+
edges: [
|
|
13350
|
+
parentEdge,
|
|
13351
|
+
...referencesEdges,
|
|
13352
|
+
...lookupEdges,
|
|
13353
|
+
...rollupEdges,
|
|
13354
|
+
...valueSetEdges
|
|
13355
|
+
]
|
|
13092
13356
|
});
|
|
13093
13357
|
};
|
|
13094
13358
|
}
|
|
@@ -14639,7 +14903,7 @@ var init_sharing_rules = __esm({
|
|
|
14639
14903
|
|
|
14640
14904
|
// ../extractors/dist/src/enterprise-metadata.js
|
|
14641
14905
|
import { readFile as readFile34 } from "node:fs/promises";
|
|
14642
|
-
var EXTRACTOR_SOURCE14, readText, extractXmlValues, USER_CRITERIA_PROFILE_ID_RE, UNRESOLVED_PROFILE_PREFIX, extractUserCriteriaProfileIds, inferReportObjectApiName, RECORD_CONTEXT_PREFIX, rescopeRecordContextField, extractFieldRefs, LIST_VIEW_FILTER_PSEUDO_FIELDS, isLegacyDottedAddress, isWellFormedColumnField, isWellFormedFilterField, extractListViewFilterRefs, REPORT_DETAIL_LIST_CAP, extractReportCriteriaItems, parseReportGroupingBlock, resolveReportFieldToken, extractReportDetail, extractReportTypeDetail, extractTransactionSecurityAction, extractMilestoneDetails, readListViewSharedTo, makeNode2, extractEnterpriseMetadata, extractReport, extractDashboard, extractListView, extractReportType, extractCustomPermission, extractCertificate, extractTransactionSecurityPolicy, FLEXIPAGE_PAGE_TYPES, extractFlexiPagePermissionRefs, extractFlexiPageEmbeddedFlows, extractFlexiPage, extractPermissionSetGroup, extractRestrictionRule, extractScopingRule, extractEntitlementProcess, extractMilestoneType, extractServiceChannel, extractQueueRoutingConfig, extractPresenceUserConfig;
|
|
14906
|
+
var EXTRACTOR_SOURCE14, readText, extractXmlValues, USER_CRITERIA_PROFILE_ID_RE, UNRESOLVED_PROFILE_PREFIX, extractUserCriteriaProfileIds, inferReportObjectApiName, RECORD_CONTEXT_PREFIX, rescopeRecordContextField, extractFieldRefs, LIST_VIEW_FILTER_PSEUDO_FIELDS, isLegacyDottedAddress, isWellFormedColumnField, isWellFormedFilterField, extractListViewFilterRefs, REPORT_DETAIL_LIST_CAP, extractReportCriteriaItems, parseReportGroupingBlock, resolveReportFieldToken, extractReportDetail, extractReportTypeDetail, extractTransactionSecurityAction, extractMilestoneDetails, readListViewSharedTo, makeNode2, extractEnterpriseMetadata, extractReport, extractDashboard, extractListView, extractReportType, extractCustomPermission, extractCertificate, extractTransactionSecurityPolicy, FLEXIPAGE_PAGE_TYPES, extractFlexiPagePermissionRefs, extractFlexiPageEmbeddedFlows, RELATED_LIST_BLOCK_RE, RELATED_LIST_API_NAME_RE, RELATED_LIST_ALIASES_RE, VALUE_RE, extractFlexiPageRelatedListFieldRefs, extractFlexiPage, extractPermissionSetGroup, extractRestrictionRule, extractScopingRule, extractEntitlementProcess, extractMilestoneType, extractServiceChannel, extractQueueRoutingConfig, extractPresenceUserConfig;
|
|
14643
14907
|
var init_enterprise_metadata = __esm({
|
|
14644
14908
|
"../extractors/dist/src/enterprise-metadata.js"() {
|
|
14645
14909
|
"use strict";
|
|
@@ -15284,6 +15548,27 @@ var init_enterprise_metadata = __esm({
|
|
|
15284
15548
|
}
|
|
15285
15549
|
return [...seen].sort();
|
|
15286
15550
|
};
|
|
15551
|
+
RELATED_LIST_BLOCK_RE = /<componentInstance>[\s\S]*?<\/componentInstance>/g;
|
|
15552
|
+
RELATED_LIST_API_NAME_RE = /<name>relatedListApiName<\/name>\s*<value>([^<]+)<\/value>/;
|
|
15553
|
+
RELATED_LIST_ALIASES_RE = /<name>relatedListFieldAliases<\/name>\s*<valueList>([\s\S]*?)<\/valueList>/;
|
|
15554
|
+
VALUE_RE = /<value>([^<]+)<\/value>/g;
|
|
15555
|
+
extractFlexiPageRelatedListFieldRefs = (xml) => {
|
|
15556
|
+
const out = [];
|
|
15557
|
+
for (const block of xml.match(RELATED_LIST_BLOCK_RE) ?? []) {
|
|
15558
|
+
const relatedListApiName = RELATED_LIST_API_NAME_RE.exec(block)?.[1]?.trim();
|
|
15559
|
+
const aliasList = RELATED_LIST_ALIASES_RE.exec(block)?.[1];
|
|
15560
|
+
if (relatedListApiName === void 0 || relatedListApiName.length === 0 || aliasList === void 0) {
|
|
15561
|
+
continue;
|
|
15562
|
+
}
|
|
15563
|
+
const fields = [
|
|
15564
|
+
...new Set([...aliasList.matchAll(VALUE_RE)].map((m) => m[1]?.trim() ?? "").filter((v) => v.length > 0))
|
|
15565
|
+
].sort();
|
|
15566
|
+
if (fields.length === 0)
|
|
15567
|
+
continue;
|
|
15568
|
+
out.push({ relatedListApiName, fields });
|
|
15569
|
+
}
|
|
15570
|
+
return out;
|
|
15571
|
+
};
|
|
15287
15572
|
extractFlexiPage = async (path2) => {
|
|
15288
15573
|
const text = await readText(path2);
|
|
15289
15574
|
if (!text.ok)
|
|
@@ -15296,6 +15581,7 @@ var init_enterprise_metadata = __esm({
|
|
|
15296
15581
|
const fieldRefs = extractFieldRefs(text.value, sobjectType).refs;
|
|
15297
15582
|
const permissionRefs = extractFlexiPagePermissionRefs(text.value);
|
|
15298
15583
|
const embeddedFlows = extractFlexiPageEmbeddedFlows(text.value);
|
|
15584
|
+
const relatedListFieldRefs = extractFlexiPageRelatedListFieldRefs(text.value);
|
|
15299
15585
|
const edges = fieldRefs.map((fieldId) => ({
|
|
15300
15586
|
fromId: nodeId,
|
|
15301
15587
|
toId: fieldId,
|
|
@@ -15342,7 +15628,8 @@ var init_enterprise_metadata = __esm({
|
|
|
15342
15628
|
fieldRefs,
|
|
15343
15629
|
rawReferenceCount: fieldRefs.length,
|
|
15344
15630
|
permissionRefs,
|
|
15345
|
-
embeddedFlows
|
|
15631
|
+
embeddedFlows,
|
|
15632
|
+
...relatedListFieldRefs.length > 0 ? { relatedListFieldRefs } : {}
|
|
15346
15633
|
});
|
|
15347
15634
|
return ok({ nodes: [node], edges });
|
|
15348
15635
|
};
|
|
@@ -16376,7 +16663,7 @@ var init_escalation_rule = __esm({
|
|
|
16376
16663
|
conditionSources.push(entry.conditionSource);
|
|
16377
16664
|
}
|
|
16378
16665
|
}
|
|
16379
|
-
const { conditionNodes, firesWhenEdges, conditionsMirror } = extractConditions({
|
|
16666
|
+
const { conditionNodes, firesWhenEdges, conditionsMirror, conditionFieldEdges } = extractConditions({
|
|
16380
16667
|
parentId: ruleId,
|
|
16381
16668
|
sources: conditionSources,
|
|
16382
16669
|
parentSourcePath: path2,
|
|
@@ -16445,7 +16732,7 @@ var init_escalation_rule = __esm({
|
|
|
16445
16732
|
}
|
|
16446
16733
|
}
|
|
16447
16734
|
}
|
|
16448
|
-
edges.push(...firesWhenEdges);
|
|
16735
|
+
edges.push(...firesWhenEdges, ...conditionFieldEdges);
|
|
16449
16736
|
return ok({ node, edges, conditionNodes });
|
|
16450
16737
|
};
|
|
16451
16738
|
extractEscalationRule = async (path2) => {
|
|
@@ -17427,7 +17714,7 @@ var init_flow_dataflow = __esm({
|
|
|
17427
17714
|
// ../extractors/dist/src/flow.js
|
|
17428
17715
|
import { readFile as readFile43 } from "node:fs/promises";
|
|
17429
17716
|
import { XMLParser as XMLParser33, XMLValidator as XMLValidator33 } from "fast-xml-parser";
|
|
17430
|
-
var FLOW_FILE_SUFFIX, ROOT_ELEMENT32, FLOW_XML_PARSER_OPTIONS, REQUIRED_ELEMENTS11, ALLOWED_STATUS, EDGE_SOURCE3, RECORD_TRIGGER_TYPES, RECORD_SCOPED_TRIGGER_TYPES, unwrapSingle34, toArray18, toNullableString3, toNonEmptyString2, readAndValidateXml32, validateRoot23, extractStartProperties, ASYNC_AFTER_COMMIT_PATH, extractScheduledPathTypes, buildStartEdge, buildPlatformEventListensToEdge, buildActionCallEdges, buildSubflowEdges, collectActionCallSummaries, buildRecordLookupEdges, ASSIGNED_VALUE_LITERAL_WRAPPERS, parseAssignedValue, attachDataflowTrace, buildInputAssignmentEdges, buildDataflowReadEdges, buildRecordCreateEdges, WHOLE_RECORD_DISCLOSURE, resolveInputReferenceObject, buildObjectDmlProps, buildRecordUpdateEdges, buildRecordDeleteEdges, BEFORE_SAVE_TRIGGER_TYPE, asRecord, stripMergeWrapper, parseRecordFieldAssignTarget, buildBeforeSaveFieldAssignmentEdges, parseFlowConditionTriplet, buildFlowDecisionSourceName, collectFlowConditionSources, edgeOperation, dedupeAndSortEdges, FAULTABLE_ELEMENTS, analyzeFaultCoverage, FLOW_LABEL_REF_PATTERN, buildLabelReferenceEdges, extractFlow;
|
|
17717
|
+
var FLOW_FILE_SUFFIX, ROOT_ELEMENT32, FLOW_XML_PARSER_OPTIONS, REQUIRED_ELEMENTS11, ALLOWED_STATUS, EDGE_SOURCE3, RECORD_TRIGGER_TYPES, RECORD_SCOPED_TRIGGER_TYPES, unwrapSingle34, toArray18, toNullableString3, toNonEmptyString2, readAndValidateXml32, validateRoot23, extractStartProperties, ASYNC_AFTER_COMMIT_PATH, extractScheduledPathTypes, buildStartEdge, buildPlatformEventListensToEdge, buildActionCallEdges, buildSubflowEdges, collectActionCallSummaries, buildRecordLookupEdges, ASSIGNED_VALUE_LITERAL_WRAPPERS, parseAssignedValue, attachDataflowTrace, buildInputAssignmentEdges, buildDataflowReadEdges, buildRecordCreateEdges, WHOLE_RECORD_DISCLOSURE, resolveInputReferenceObject, buildObjectDmlProps, buildRecordUpdateEdges, buildRecordDeleteEdges, BEFORE_SAVE_TRIGGER_TYPE, asRecord, stripMergeWrapper, parseRecordFieldAssignTarget, buildBeforeSaveFieldAssignmentEdges, pickTripletMember, parseFlowConditionTriplet, buildFlowDecisionSourceName, collectFlowConditionSources, edgeOperation, dedupeAndSortEdges, FAULTABLE_ELEMENTS, analyzeFaultCoverage, FLOW_LABEL_REF_PATTERN, buildLabelReferenceEdges, extractFlow;
|
|
17431
17718
|
var init_flow = __esm({
|
|
17432
17719
|
"../extractors/dist/src/flow.js"() {
|
|
17433
17720
|
"use strict";
|
|
@@ -18078,19 +18365,27 @@ var init_flow = __esm({
|
|
|
18078
18365
|
}
|
|
18079
18366
|
return edges;
|
|
18080
18367
|
};
|
|
18368
|
+
pickTripletMember = (obj, keys) => {
|
|
18369
|
+
for (const key of keys) {
|
|
18370
|
+
const v = unwrapSingle34(obj[key]);
|
|
18371
|
+
if (v !== void 0 && v !== null && v !== "")
|
|
18372
|
+
return v;
|
|
18373
|
+
}
|
|
18374
|
+
return null;
|
|
18375
|
+
};
|
|
18081
18376
|
parseFlowConditionTriplet = (raw) => {
|
|
18082
18377
|
if (typeof raw !== "object" || raw === null)
|
|
18083
18378
|
return null;
|
|
18084
18379
|
const obj = raw;
|
|
18085
|
-
const fieldRaw =
|
|
18086
|
-
if (fieldRaw ===
|
|
18380
|
+
const fieldRaw = pickTripletMember(obj, ["leftValueReference", "field"]);
|
|
18381
|
+
if (fieldRaw === null) {
|
|
18087
18382
|
return null;
|
|
18088
18383
|
}
|
|
18089
18384
|
const operatorRaw = unwrapSingle34(obj["operator"]);
|
|
18090
18385
|
if (operatorRaw === void 0 || operatorRaw === null || operatorRaw === "") {
|
|
18091
18386
|
return null;
|
|
18092
18387
|
}
|
|
18093
|
-
const rightValueRaw =
|
|
18388
|
+
const rightValueRaw = pickTripletMember(obj, ["rightValue", "value"]);
|
|
18094
18389
|
let value = null;
|
|
18095
18390
|
if (typeof rightValueRaw === "object" && rightValueRaw !== null) {
|
|
18096
18391
|
const wrapper = rightValueRaw;
|
|
@@ -18327,14 +18622,14 @@ var init_flow = __esm({
|
|
|
18327
18622
|
rawEdges.push(...buildLabelReferenceEdges(flowId, xmlResult.value));
|
|
18328
18623
|
const edges = dedupeAndSortEdges(rawEdges);
|
|
18329
18624
|
const conditionSources = collectFlowConditionSources(rootObj);
|
|
18330
|
-
const { conditionNodes, firesWhenEdges, conditionsMirror } = extractConditions({
|
|
18625
|
+
const { conditionNodes, firesWhenEdges, conditionsMirror, conditionFieldEdges } = extractConditions({
|
|
18331
18626
|
parentId: flowId,
|
|
18332
18627
|
sources: conditionSources,
|
|
18333
18628
|
parentSourcePath: path2,
|
|
18334
18629
|
parentApiVersion: apiVersion,
|
|
18335
18630
|
parentObjectApiName: startProps.triggerObject
|
|
18336
18631
|
});
|
|
18337
|
-
const allEdges = [...edges, ...firesWhenEdges];
|
|
18632
|
+
const allEdges = [...edges, ...firesWhenEdges, ...conditionFieldEdges];
|
|
18338
18633
|
const node = {
|
|
18339
18634
|
id: flowId,
|
|
18340
18635
|
type: "Flow",
|
|
@@ -25530,7 +25825,7 @@ var init_validation_rule = __esm({
|
|
|
25530
25825
|
const errorMessage = String(unwrapSingle66(rootObj["errorMessage"]));
|
|
25531
25826
|
const active = coerceBoolean40(unwrapSingle66(rootObj["active"]));
|
|
25532
25827
|
const conditionSources = errorConditionFormula.length === 0 ? [] : [{ kind: "formula", expression: errorConditionFormula }];
|
|
25533
|
-
const { conditionNodes, firesWhenEdges, conditionsMirror } = extractConditions({
|
|
25828
|
+
const { conditionNodes, firesWhenEdges, conditionsMirror, conditionFieldEdges } = extractConditions({
|
|
25534
25829
|
parentId: nodeId,
|
|
25535
25830
|
sources: conditionSources,
|
|
25536
25831
|
parentSourcePath: path2,
|
|
@@ -25566,7 +25861,7 @@ var init_validation_rule = __esm({
|
|
|
25566
25861
|
const referencesEdges = buildReferencesEdges(errorConditionFormula, nodeId, objectApiName, FORMULA_ELEMENT_NAME2);
|
|
25567
25862
|
return ok({
|
|
25568
25863
|
nodes: [node, ...conditionNodes],
|
|
25569
|
-
edges: [parentEdge, ...referencesEdges, ...firesWhenEdges]
|
|
25864
|
+
edges: [parentEdge, ...referencesEdges, ...firesWhenEdges, ...conditionFieldEdges]
|
|
25570
25865
|
});
|
|
25571
25866
|
};
|
|
25572
25867
|
}
|
|
@@ -26564,7 +26859,7 @@ var init_src6 = __esm({
|
|
|
26564
26859
|
|
|
26565
26860
|
// ../mcp/dist/src/prompts.js
|
|
26566
26861
|
import { ErrorCode, GetPromptRequestSchema, ListPromptsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
26567
|
-
var userMessage, GENERATOR_PREAMBLE, PLAYBOOK_PREAMBLE, PROMPTS, PROMPT_BY_NAME, MCP_PROMPTS, getMcpPrompt, registerPrompts;
|
|
26862
|
+
var userMessage, GENERATOR_PREAMBLE, PLAYBOOK_PREAMBLE, FIELD_AUDIT_PREAMBLE, PROMPTS, PROMPT_BY_NAME, MCP_PROMPTS, getMcpPrompt, registerPrompts;
|
|
26568
26863
|
var init_prompts = __esm({
|
|
26569
26864
|
"../mcp/dist/src/prompts.js"() {
|
|
26570
26865
|
"use strict";
|
|
@@ -26583,6 +26878,9 @@ var init_prompts = __esm({
|
|
|
26583
26878
|
2. Live steps need the opt-in live plane. If off, run offline steps, then offer \`sfi.live_consent { grant: true }\` and re-run live steps \u2014 never infer live facts from the vault.
|
|
26584
26879
|
3. Cite canonical component ids; stamp provenance (\`offline_snapshot\` / \`live_org\`) and confidence. Use any tool's \`rendered\` field verbatim when present.
|
|
26585
26880
|
4. Single-fact questions belong on \`sfi.route_question\`, not a playbook.`;
|
|
26881
|
+
FIELD_AUDIT_PREAMBLE = `You are running an sf-intelligence FIELD AUDIT \u2014 deciding whether fields can be deleted, and what must happen first. This is a document that authorises destruction, so its value is entirely a function of whether the reader can tell your evidence apart from your inference.
|
|
26882
|
+
|
|
26883
|
+
The one principle everything else follows from: **population tells you how much data is in a field; it never tells you what depends on it.** The two agree on busy, obviously-live fields and diverge everywhere else \u2014 formula fields, transient state flags, integration keys, anything frozen. Every serious error in this kind of analysis comes from substituting one for the other.`;
|
|
26586
26884
|
PROMPTS = [
|
|
26587
26885
|
{
|
|
26588
26886
|
meta: {
|
|
@@ -26831,6 +27129,66 @@ Playbook \`health\` \u2014 operational pulse (mostly live):
|
|
|
26831
27129
|
Report: red/amber/green pulse with specific failing signals named.`)
|
|
26832
27130
|
]
|
|
26833
27131
|
})
|
|
27132
|
+
},
|
|
27133
|
+
{
|
|
27134
|
+
meta: {
|
|
27135
|
+
name: "sfi.field_audit",
|
|
27136
|
+
description: "Field-deletion audit: decide Keep / Review / Deprecate-then-Remove / Remove for a field or an object\u2019s fields, tracing every dependency first. Also validates an existing field-cleanup analysis.",
|
|
27137
|
+
arguments: [
|
|
27138
|
+
{
|
|
27139
|
+
name: "objectApiName",
|
|
27140
|
+
description: "The object whose fields are under audit (e.g. Account or MyObject__c).",
|
|
27141
|
+
required: true
|
|
27142
|
+
},
|
|
27143
|
+
{
|
|
27144
|
+
name: "fieldApiNames",
|
|
27145
|
+
description: "Optional comma-separated field api names to scope the audit. Omit to assess the whole object.",
|
|
27146
|
+
required: false
|
|
27147
|
+
}
|
|
27148
|
+
]
|
|
27149
|
+
},
|
|
27150
|
+
build: (args) => ({
|
|
27151
|
+
description: "Field-deletion audit with dependency tracing.",
|
|
27152
|
+
messages: [
|
|
27153
|
+
userMessage(`${FIELD_AUDIT_PREAMBLE}
|
|
27154
|
+
|
|
27155
|
+
Object under audit: \`${args?.["objectApiName"] ?? "<objectApiName>"}\`${args?.["fieldApiNames"] !== void 0 && args["fieldApiNames"].length > 0 ? `
|
|
27156
|
+
Fields scoped to: ${args["fieldApiNames"]}` : "\nScope: every custom field on the object."}
|
|
27157
|
+
|
|
27158
|
+
Run in this order. Do not skip step 0 \u2014 it is what makes the rest trustworthy.
|
|
27159
|
+
|
|
27160
|
+
0. **Orient and calibrate.**
|
|
27161
|
+
- \`sfi.org_card\` for component counts and DECLARED coverage gaps.
|
|
27162
|
+
- \`sfi.coverage_report\`. If Report/Dashboard reads \`pending\`, the report pull was capped (default \`SFI_REPORTS_CAP\`=500, ranked by usage) \u2014 say so, and treat every report count as a floor, not a total.
|
|
27163
|
+
- **Positive control:** pick a field you can already prove is referenced, run the same tools against it, and confirm they return something. A zero from an uncalibrated method is not a finding.
|
|
27164
|
+
|
|
27165
|
+
1. **Per field, gather \u2014 do not judge yet.**
|
|
27166
|
+
- \`sfi.field_360\` \u2014 full profile across validation, formulas, writers, readers, UI, integrations.
|
|
27167
|
+
- \`sfi.find_field_anywhere\` \u2014 every incoming edge grouped by component type.
|
|
27168
|
+
- \`sfi.safe_to_delete_field\` \u2014 the verdict with its reasoning chain. Use \`format: 'checklist'\` when you want ordered pre-work.
|
|
27169
|
+
- \`sfi.find_formula_references\` for formula referrers.
|
|
27170
|
+
|
|
27171
|
+
2. **Read the structure before any number.** \`required\` + \`unique\` + \`externalId\` with no in-org writer is an integration upsert key \u2014 the strongest possible Keep, and the exact shape a naive "nothing writes it" reading calls dead. \`trackHistory: true\` means deleting the field destroys history rows permanently; no export of current values recovers them.
|
|
27172
|
+
|
|
27173
|
+
3. **A formula field has no population figure.** If the body references \`$User\`, \`$UserRole\`, \`$Profile\` or \`TODAY()\`, the percentage measures who ran the query. If it returns \`IF(...,1,0)\` or \`CASESAFEID(Id)\` it can never be null and measures arithmetic. Read the body; strike the number.
|
|
27174
|
+
|
|
27175
|
+
4. **Measure flow, not just stock** (live plane only, and only with consent): \`sfi.live_field_population\`, then \`MAX(field)\` and a future-dated count via \`sfi.live_aggregate\`. A field with 750,000 values can be frozen; a field at 0% can be load-bearing. Watch \`sfi.live_budget\`; when it runs out, say which figures are carried forward UNVERIFIED.
|
|
27176
|
+
|
|
27177
|
+
5. **Record ROLE, never count.** For each consumer, what would it do if the field vanished? A field used as a report or list-view **filter** does not empty the report when deleted \u2014 it silently WIDENS it. Nothing errors, so nobody reports it. That is strictly more dangerous than a lost display column.
|
|
27178
|
+
|
|
27179
|
+
Verdict vocabulary \u2014 four values, not three:
|
|
27180
|
+
- **Keep** \u2014 live dependency, integration contract, or clear business value. Name the blocker, never the population.
|
|
27181
|
+
- **Review** \u2014 genuinely ambiguous. INCOMPLETE unless it names the exact question and the named human who answers it.
|
|
27182
|
+
- **Deprecate-then-Remove** \u2014 dead in practice but holds data, history, or cosmetic references. Needs staged retirement.
|
|
27183
|
+
- **Remove** \u2014 no data of value, no live dependency, every surface checked-negative with a proven method.
|
|
27184
|
+
|
|
27185
|
+
Honesty rules, non-negotiable:
|
|
27186
|
+
- Render any \`coverageCaveat\` BEFORE the verdict, never as a footnote.
|
|
27187
|
+
- Separate "checked and found nothing" from "could not check". They look identical in a report and mean opposite things.
|
|
27188
|
+
- Name the blind spots no static analysis closes: dynamically-built SOQL, reflective \`.get('Field__c')\`, field lists stored as ORG DATA in custom settings or custom metadata (invisible to metadata retrieval AND to this vault \u2014 they fail at runtime, not at deploy), external ETL job definitions, managed-package internals, private report folders, and email-template merge fields.
|
|
27189
|
+
- Never present an \`sfi.safe_to_delete_field\` verdict of \`safe\` as permission to delete. It means no modelled dependency was found, which is a statement about coverage as much as about the field.`)
|
|
27190
|
+
]
|
|
27191
|
+
})
|
|
26834
27192
|
}
|
|
26835
27193
|
];
|
|
26836
27194
|
PROMPT_BY_NAME = new Map(PROMPTS.map((p) => [p.meta.name, p]));
|
|
@@ -38280,6 +38638,15 @@ var init_coverage_trust = __esm({
|
|
|
38280
38638
|
"VisualforceComponent",
|
|
38281
38639
|
"QuickAction",
|
|
38282
38640
|
"WorkflowRule",
|
|
38641
|
+
// The remaining condition firers wired to extractConditions. Their
|
|
38642
|
+
// ConditionalContext nodes now emit `readsFrom` edges to the fields their
|
|
38643
|
+
// criteria TEST, so a field can carry a `condition` blocker hiding behind
|
|
38644
|
+
// any of these families — and without them listed, buildCoverageCaveat
|
|
38645
|
+
// stayed silent on an unretrieved family while the verdict read clean.
|
|
38646
|
+
"ApprovalProcess",
|
|
38647
|
+
"AssignmentRule",
|
|
38648
|
+
"AutoResponseRule",
|
|
38649
|
+
"EscalationRule",
|
|
38283
38650
|
"SharingRule",
|
|
38284
38651
|
"Report",
|
|
38285
38652
|
"Dashboard",
|
|
@@ -42302,7 +42669,7 @@ var init_roster = __esm({
|
|
|
42302
42669
|
},
|
|
42303
42670
|
{
|
|
42304
42671
|
name: "sfi.safe_to_delete_field",
|
|
42305
|
-
description: "Is this field safe to delete, and what's the evidence? The BA's due-diligence check before retiring a field \"no one uses\" \u2014 it gathers every dependency (validation rules, flows, formulas, layouts, Apex, LWC) so you can prove whether anything still relies on it before signing off. Given a CustomField canonical id (`CustomField:{Object}.{Field}`), composes every incoming dependency edge into a confidence-weighted deletion verdict: `safe` (no incoming edges), `risky` (Apex/LWC references that need spot-checking \u2014 Apex evidence includes parsed-confidence AST edges covering dot-access AND fields referenced only inside inline static SOQL or constant-string Database.query literals, plus heuristic regex-scanner matches; string-BUILT dynamic SOQL stays invisible), `blocking` (declared Flow/ValidationRule/
|
|
42672
|
+
description: "Is this field safe to delete, and what's the evidence? The BA's due-diligence check before retiring a field \"no one uses\" \u2014 it gathers every dependency (validation rules, flows, formulas, roll-up summaries, automation conditions, layouts, Apex, LWC) so you can prove whether anything still relies on it before signing off. Given a CustomField canonical id (`CustomField:{Object}.{Field}`), composes every incoming dependency edge into a confidence-weighted deletion verdict: `safe` (no incoming edges), `risky` (Apex/LWC references that need spot-checking \u2014 Apex evidence includes parsed-confidence AST edges covering dot-access AND fields referenced only inside inline static SOQL or constant-string Database.query literals, plus heuristic regex-scanner matches; string-BUILT dynamic SOQL stays invisible), `blocking` (declared Flow/ValidationRule/formula dependencies the platform will refuse to drop, plus two coupling classes declared on OTHER components: `rollup` \u2014 a roll-up summary on the PARENT object that aggregates, anchors on, or filters by this field, with each example naming its own `rollupRole` (`summarizedField` / `summaryForeignKey` / `summaryFilterItem`) so the citation is the coupling that actually exists; and `condition` \u2014 a criterion that TESTS this field, minted from any of the seven wired firer families: Flow entry criteria, Flow decisions, validation-rule conditions, workflow-rule criteria, and approval-process, assignment-rule, auto-response-rule and escalation-rule criteria. Each condition example carries the `firerId` of the component whose criteria it is (the example `id` is a synthetic ConditionalContext node), so an approval-process blocker is cited as an approval process rather than as a Flow. Conditions are listed, never evaluated: the vault does not know whether a runtime record satisfies them. ONE REFERRER IS COUNTED ONCE: a validation rule whose `errorConditionFormula` both references and tests a field reaches it by TWO edges tokenized from that one string, so it is reported once under `validation` (the truthful label \u2014 not `formula`, which means \"another formula FIELD references this\") and the folded row is disclosed on that example as `alsoVia: ['condition']` rather than counted again. Additive conditions never fold: a rule testing a field its direct reference could not resolve, or a Flow that WRITES and separately TESTS one field, each keep their own category, count and citation. Formula examples likewise carry the `traversalPath` (`Parent__r.Field__c`) when the reference was resolved through a cross-object relationship rather than tokenized directly), `unknown` (only unrecognised edges), or `review` (NOT proven safe \u2014 incomplete coverage, OR a standard / managed-package field with no node of its own but referenced by edges: it is reviewed from those edges with a not-modeled caveat instead of returning component-not-found; B12). Each category in the `reasoning` array carries its referrer count, up to 5 example referrers (full list via `sfi.get_impact`), and a per-category note explaining the honesty boundary. When an incoming edge carries `properties.confirmedByApi` (stamped by `sfi refresh --with-tooling-api` MetadataComponentDependency enrichment), reasoning entries/examples surface `apiConfirmed: true` as additive evidence \u2014 it does not change the verdict cascade; the tool never calls the Tooling API at query time. Pass `format: 'checklist'` to also get a `checklist` \u2014 a \"before you delete X\" Markdown checklist rendered from the verdict + reasoning, with the `coverageCaveat` surfaced FIRST (never footnoted) and removal steps ordered most-severe-first. It PROPOSES a checklist for a human and never deletes or writes to the org. Pass `format: 'proposal'` (Finding #35) to also get a `proposal` \u2014 a deploy-ready, LOCAL artifact for the field: a populated `destructiveChanges.xml` (the field) + an empty `package.xml`, each led by an evidence comment carrying the verdict, every dependency finding, the vault `sourceTreeHash`, and a REVIEW-before-deploy banner. sfi returns the file STRINGS only \u2014 it NEVER deploys, writes, or connects to the org; a human feeds the files to their own deploy tool (Gearset / Copado / `sf project deploy`). Validated against `docs/schemas/proposal.schema.json`. Emitted for EVERY verdict (a `blocking` field's proposal leads with `verdict: blocking` so it never reads as safe). When the vault holds captured data-shape facts, the response embeds the field's sampled fill rate as a stamped `data_snapshot` `dataShape` block \u2014 CONTEXT ONLY: the verdict is computed purely from the metadata graph and never moves toward safe on a sampled observation. When the heuristic PII recognizer classifies the field pii/sensitive, the response carries a `piiCompliance` block (non-verdict-lowering compliance escalation, with classification/category/message) and the checklist surfaces it FIRST, above the coverage caveat and verdict; a PII field never reads as a bland safe. Heuristic \u2014 absence of the block is NOT a clearance. HYBRID (CR-CAP-L5): when the static verdict would be `safe`, pass `liveEnabled: true` (or grant consent) to cross-check the field's LIVE production population before trusting that verdict \u2014 real data despite zero static references may be written by dynamic Apex, an integration, or another blind spot the scanner cannot see. A `populatedCount > 0` DOWNGRADES the verdict from `safe` to `review` and attaches a `livePopulation` evidence block (`objectApiName`/`fieldApiName`/`totalCount`/`populatedCount`/`populationRate`/`liveQueriedAt`); a zero-population result leaves `safe` standing but still attaches the evidence block, confirming the cross-check ran. The live plane is NEVER a hard dependency \u2014 offline stays fully functional; when it is off, unavailable, or the query errors (budget exhausted, org unreachable), the response fails soft to the disclosed static verdict with a `trust.limitations` line naming the missing evidence ('static-only verdict; live population not checked'). UPGRADE PATH: the roll-up coupling, condition and resolved formula-traversal edge families were added in 0.3.0, so a vault BUILT by an older sf-intelligence does not hold them and their absence proves nothing. When `manifest.version` is older than the running plugin the response carries a `builderVersionCaveat` (also mirrored into `trust.limitations` and surfaced above the verdict in the checklist) and an otherwise-`safe` verdict is reported as `review` \u2014 NOT proven safe \u2014 until `sfi refresh` rebuilds the vault. The coverage caveat cannot catch this: those metadata families WERE retrieved; the extractor that reads them did not exist yet.",
|
|
42306
42673
|
inputSchema: SAFE_TO_DELETE_FIELD_INPUT_SCHEMA
|
|
42307
42674
|
},
|
|
42308
42675
|
{
|
|
@@ -42780,7 +43147,7 @@ var init_roster = __esm({
|
|
|
42780
43147
|
// cross-tier field-forensics questions.
|
|
42781
43148
|
{
|
|
42782
43149
|
name: "sfi.field_360",
|
|
42783
|
-
description: "A complete 360 profile of a single FIELD \u2014 everything that touches that field across validation, formulas, automation, code, UI, integrations, and emails, in one place. (Field forensics \u2014 this is about a field, NOT a user Profile or permissions.) The go-to answer for \"give me the full profile of this field\", \"the full picture of {Object}.{Field}\", \"everything that uses {Object}.{Field}\", \"what touches this field across automation and code\", or a BA's field impact assessment before a change. v3.0 unified field-forensics synthesis tool. Given a CustomField canonical id (or short `<Object>.<Field>` form), composes every prior tier's reads of the field into one structured response with ten optional content sections (`validates`, `formulas`, `writers`, `readers`, `ui`, `integrations`, `automations`, `emails`, `dependencies`, `summary`) plus the v3.0 constitutional honesty axis. Optional `includeSections` narrows the response; `maxRowsPerSection` (default 50, max 200) bounds per-section row counts; `groupBy` (default `'source'`) reshuffles the rendering hint. The `summary` carries per-section unfiltered counts AND a `riskLevel` (`'low' | 'medium' | 'high'`) computed per PLAN-v3.0 \xA74.1 with the specific `riskFactors[]` enumerated; PII risk factors are computed live via the heuristic pii recognizer (EncryptedText / SSN / financial names), not read from a stamped property. The `boundaries[]` array carries the verbatim Q165 disclosure naming which surfaces are composed vs folded elsewhere (list views, reports, dashboards); `dataNotAvailable: ['list-view-filters', 'reports', 'dashboards']` surfaces verbatim regardless of section filter. D3: a Flow decision / record-trigger filter that references this field carries NO `readsFrom` edge (it is a `firesWhen` edge to a ConditionalContext), so such Flow reads are RECONSTRUCTED from the graph's ConditionalContext `fieldRefs` and surfaced in `readers` as heuristic-confidence rows (`source: flow-condition-reads-scan:*`, deduped against real `readsFrom` readers) \u2014 otherwise `readers` would read 0 for a field several Flows filter on; `boundaries[]` also names the referrer classes still NOT composed into any section (roll-up source coupling, layout related-list placement). Top-level `confidence` reports `'mixed'` when sections span tiers (the typical case for any real-org field). Honesty axis (verbatim, ALWAYS): synthesis without omission disclosure is a contract violation; the report is the COMPLETE answer ONLY for extracted axes. Invalid prefix surfaces as `invalid-query`; unknown ids surface as `component-not-found`. When the vault holds captured data-shape facts (`refresh --with-data-shape`), the response embeds a `dataShape` block \u2014 the field's sampled fill rate as a stamped `data_snapshot` (sampled + TTL-checked; context, never a live read).",
|
|
43150
|
+
description: "A complete 360 profile of a single FIELD \u2014 everything that touches that field across validation, formulas, automation, code, UI, integrations, and emails, in one place. (Field forensics \u2014 this is about a field, NOT a user Profile or permissions.) The go-to answer for \"give me the full profile of this field\", \"the full picture of {Object}.{Field}\", \"everything that uses {Object}.{Field}\", \"what touches this field across automation and code\", or a BA's field impact assessment before a change. v3.0 unified field-forensics synthesis tool. Given a CustomField canonical id (or short `<Object>.<Field>` form), composes every prior tier's reads of the field into one structured response with ten optional content sections (`validates`, `formulas`, `writers`, `readers`, `ui`, `integrations`, `automations`, `emails`, `dependencies`, `summary`) plus the v3.0 constitutional honesty axis. A validation rule that reaches the field BOTH ways (a direct `references` edge and its own condition's `readsFrom`, both tokenized from one `errorConditionFormula`) is listed ONCE under `validates` \u2014 `validates` and `automations` never both hold one rule \u2014 with the fold named in `boundaries[]`; the folded rows still count on the automation RISK axis, so `riskLevel` is unaffected by the collapse. Optional `includeSections` narrows the response; `maxRowsPerSection` (default 50, max 200) bounds per-section row counts; `groupBy` (default `'source'`) reshuffles the rendering hint. The `summary` carries per-section unfiltered counts AND a `riskLevel` (`'low' | 'medium' | 'high'`) computed per PLAN-v3.0 \xA74.1 with the specific `riskFactors[]` enumerated; PII risk factors are computed live via the heuristic pii recognizer (EncryptedText / SSN / financial names), not read from a stamped property. The `boundaries[]` array carries the verbatim Q165 disclosure naming which surfaces are composed vs folded elsewhere (list views, reports, dashboards); `dataNotAvailable: ['list-view-filters', 'reports', 'dashboards']` surfaces verbatim regardless of section filter. D3: a Flow decision / record-trigger filter that references this field carries NO `readsFrom` edge (it is a `firesWhen` edge to a ConditionalContext), so such Flow reads are RECONSTRUCTED from the graph's ConditionalContext `fieldRefs` and surfaced in `readers` as heuristic-confidence rows (`source: flow-condition-reads-scan:*`, deduped against real `readsFrom` readers) \u2014 otherwise `readers` would read 0 for a field several Flows filter on; `boundaries[]` also names the referrer classes still NOT composed into any section (roll-up source coupling, layout related-list placement). Top-level `confidence` reports `'mixed'` when sections span tiers (the typical case for any real-org field). Honesty axis (verbatim, ALWAYS): synthesis without omission disclosure is a contract violation; the report is the COMPLETE answer ONLY for extracted axes. Invalid prefix surfaces as `invalid-query`; unknown ids surface as `component-not-found`. When the vault holds captured data-shape facts (`refresh --with-data-shape`), the response embeds a `dataShape` block \u2014 the field's sampled fill rate as a stamped `data_snapshot` (sampled + TTL-checked; context, never a live read).",
|
|
42784
43151
|
inputSchema: FIELD_360_INPUT_SCHEMA
|
|
42785
43152
|
},
|
|
42786
43153
|
{
|
|
@@ -56209,18 +56576,35 @@ var init_concept_model = __esm({
|
|
|
56209
56576
|
}
|
|
56210
56577
|
});
|
|
56211
56578
|
EDGE_SEMANTICS = Object.freeze({
|
|
56212
|
-
|
|
56213
|
-
|
|
56214
|
-
|
|
56215
|
-
|
|
56216
|
-
|
|
56217
|
-
|
|
56579
|
+
bySource: [
|
|
56580
|
+
{
|
|
56581
|
+
source: "formula-tokenizer",
|
|
56582
|
+
edgeType: "references",
|
|
56583
|
+
fromType: "CustomField",
|
|
56584
|
+
category: "formula",
|
|
56585
|
+
verdict: "blocking"
|
|
56586
|
+
},
|
|
56587
|
+
{
|
|
56588
|
+
source: "rollup-summary",
|
|
56589
|
+
edgeType: "references",
|
|
56590
|
+
category: "rollup",
|
|
56591
|
+
verdict: "blocking"
|
|
56592
|
+
},
|
|
56593
|
+
{
|
|
56594
|
+
source: "relationship-resolver",
|
|
56595
|
+
edgeType: "references",
|
|
56596
|
+
fromType: "CustomField",
|
|
56597
|
+
category: "formula",
|
|
56598
|
+
verdict: "blocking"
|
|
56599
|
+
}
|
|
56600
|
+
],
|
|
56218
56601
|
byEdgeType: {
|
|
56219
56602
|
readsFrom: {
|
|
56220
56603
|
bySourceType: {
|
|
56221
56604
|
ApexClass: { category: "apex", verdict: "risky" },
|
|
56222
56605
|
ApexTrigger: { category: "apex", verdict: "risky" },
|
|
56223
56606
|
Flow: { category: "flow", verdict: "blocking" },
|
|
56607
|
+
ConditionalContext: { category: "condition", verdict: "blocking" },
|
|
56224
56608
|
LightningComponentBundle: { category: "frontend", verdict: "risky" },
|
|
56225
56609
|
AuraDefinitionBundle: { category: "frontend", verdict: "risky" }
|
|
56226
56610
|
},
|
|
@@ -61578,6 +61962,60 @@ var init_flow_field_writers_scan = __esm({
|
|
|
61578
61962
|
}
|
|
61579
61963
|
});
|
|
61580
61964
|
|
|
61965
|
+
// ../mcp/dist/src/tools/restated-condition-edges.js
|
|
61966
|
+
var FORMULA_TOKENIZER_SOURCE, CONDITION_EXTRACTOR_SOURCE, FORMULA_CONDITION_KIND, stringProp, isDirectFormulaReference, isFormulaConditionEdge, EMPTY_INDEX, indexRestatedConditionEdges;
|
|
61967
|
+
var init_restated_condition_edges = __esm({
|
|
61968
|
+
"../mcp/dist/src/tools/restated-condition-edges.js"() {
|
|
61969
|
+
"use strict";
|
|
61970
|
+
FORMULA_TOKENIZER_SOURCE = "formula-tokenizer";
|
|
61971
|
+
CONDITION_EXTRACTOR_SOURCE = "condition-extractor";
|
|
61972
|
+
FORMULA_CONDITION_KIND = "formula";
|
|
61973
|
+
stringProp = (edge, key) => {
|
|
61974
|
+
const raw = edge.properties[key];
|
|
61975
|
+
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
61976
|
+
};
|
|
61977
|
+
isDirectFormulaReference = (edge) => edge.edgeType === "references" && edge.source === FORMULA_TOKENIZER_SOURCE;
|
|
61978
|
+
isFormulaConditionEdge = (edge) => edge.edgeType === "readsFrom" && edge.source === CONDITION_EXTRACTOR_SOURCE && edge.properties["kind"] === FORMULA_CONDITION_KIND;
|
|
61979
|
+
EMPTY_INDEX = Object.freeze({
|
|
61980
|
+
isRestatingCondition: () => false,
|
|
61981
|
+
isRestatedDirectReference: () => false,
|
|
61982
|
+
suppressedConditionCount: 0
|
|
61983
|
+
});
|
|
61984
|
+
indexRestatedConditionEdges = (edges) => {
|
|
61985
|
+
const directReferrers = /* @__PURE__ */ new Set();
|
|
61986
|
+
for (const edge of edges) {
|
|
61987
|
+
if (isDirectFormulaReference(edge))
|
|
61988
|
+
directReferrers.add(edge.fromId);
|
|
61989
|
+
}
|
|
61990
|
+
if (directReferrers.size === 0)
|
|
61991
|
+
return EMPTY_INDEX;
|
|
61992
|
+
const restatingFirers = /* @__PURE__ */ new Set();
|
|
61993
|
+
let suppressedConditionCount = 0;
|
|
61994
|
+
for (const edge of edges) {
|
|
61995
|
+
if (!isFormulaConditionEdge(edge))
|
|
61996
|
+
continue;
|
|
61997
|
+
const firerId = stringProp(edge, "firerId");
|
|
61998
|
+
if (firerId === null || !directReferrers.has(firerId))
|
|
61999
|
+
continue;
|
|
62000
|
+
restatingFirers.add(firerId);
|
|
62001
|
+
suppressedConditionCount += 1;
|
|
62002
|
+
}
|
|
62003
|
+
if (restatingFirers.size === 0)
|
|
62004
|
+
return EMPTY_INDEX;
|
|
62005
|
+
return {
|
|
62006
|
+
isRestatingCondition: (edge) => {
|
|
62007
|
+
if (!isFormulaConditionEdge(edge))
|
|
62008
|
+
return false;
|
|
62009
|
+
const firerId = stringProp(edge, "firerId");
|
|
62010
|
+
return firerId !== null && restatingFirers.has(firerId);
|
|
62011
|
+
},
|
|
62012
|
+
isRestatedDirectReference: (edge) => isDirectFormulaReference(edge) && restatingFirers.has(edge.fromId),
|
|
62013
|
+
suppressedConditionCount
|
|
62014
|
+
};
|
|
62015
|
+
};
|
|
62016
|
+
}
|
|
62017
|
+
});
|
|
62018
|
+
|
|
61581
62019
|
// ../mcp/dist/src/tools/field-360.js
|
|
61582
62020
|
import { z as z52 } from "zod";
|
|
61583
62021
|
var CUSTOM_FIELD_PREFIX7, DEFAULT_MAX_ROWS_PER_SECTION, HARD_CAP_MAX_ROWS_PER_SECTION, FIELD_360_BYTE_BUDGET, FIELD_360_Q165_DISCLOSURE, SECTION_NAMES, GROUP_BY_VALUES, field360InputSchema, normalizeFieldId, AUTOMATION_NODE_TYPES, INTEGRATION_NODE_TYPES, UI_NODE_TYPES, CODE_NODE_TYPES, buildRow, compareRows, buildSection, resolveEdgeSource, detectIsFormula, extractFieldType, extractReferenceTo, computeOverallConfidence, computeRisk, detectIsPii, emptyBuckets, classifyIncomingEdge, field360Handler;
|
|
@@ -61598,6 +62036,7 @@ var init_field_360 = __esm({
|
|
|
61598
62036
|
init_phantom_node();
|
|
61599
62037
|
init_report_dashboard_usage();
|
|
61600
62038
|
init_resolve_field_or_suggest();
|
|
62039
|
+
init_restated_condition_edges();
|
|
61601
62040
|
CUSTOM_FIELD_PREFIX7 = "CustomField:";
|
|
61602
62041
|
DEFAULT_MAX_ROWS_PER_SECTION = 50;
|
|
61603
62042
|
HARD_CAP_MAX_ROWS_PER_SECTION = 200;
|
|
@@ -61606,6 +62045,7 @@ var init_field_360 = __esm({
|
|
|
61606
62045
|
SECTION_NAMES = [
|
|
61607
62046
|
"validates",
|
|
61608
62047
|
"formulas",
|
|
62048
|
+
"rollups",
|
|
61609
62049
|
"writers",
|
|
61610
62050
|
"readers",
|
|
61611
62051
|
"ui",
|
|
@@ -61753,12 +62193,12 @@ var init_field_360 = __esm({
|
|
|
61753
62193
|
return "mixed";
|
|
61754
62194
|
return [...seen][0];
|
|
61755
62195
|
};
|
|
61756
|
-
computeRisk = (perSectionCounts, isPii, isFormula, dependenciesCount) => {
|
|
62196
|
+
computeRisk = (perSectionCounts, isPii, isFormula, dependenciesCount, foldedConditionCount) => {
|
|
61757
62197
|
const writers = perSectionCounts["writers"] ?? 0;
|
|
61758
62198
|
const readers = perSectionCounts["readers"] ?? 0;
|
|
61759
62199
|
const integrations = perSectionCounts["integrations"] ?? 0;
|
|
61760
62200
|
const emails = perSectionCounts["emails"] ?? 0;
|
|
61761
|
-
const automations = perSectionCounts["automations"] ?? 0;
|
|
62201
|
+
const automations = (perSectionCounts["automations"] ?? 0) + foldedConditionCount;
|
|
61762
62202
|
const factors = [];
|
|
61763
62203
|
if (integrations >= 2) {
|
|
61764
62204
|
factors.push(`${integrations}-integrations-exceeds-threshold-2`);
|
|
@@ -61802,6 +62242,7 @@ var init_field_360 = __esm({
|
|
|
61802
62242
|
emptyBuckets = () => ({
|
|
61803
62243
|
validates: [],
|
|
61804
62244
|
formulas: [],
|
|
62245
|
+
rollups: [],
|
|
61805
62246
|
writers: [],
|
|
61806
62247
|
readers: [],
|
|
61807
62248
|
ui: [],
|
|
@@ -61817,10 +62258,14 @@ var init_field_360 = __esm({
|
|
|
61817
62258
|
buckets.validates.push(row);
|
|
61818
62259
|
return;
|
|
61819
62260
|
}
|
|
61820
|
-
if (edge.edgeType === "references" && edge.source === "formula-tokenizer") {
|
|
62261
|
+
if (edge.edgeType === "references" && (edge.source === "formula-tokenizer" || edge.source === "relationship-resolver" && source.type === "CustomField")) {
|
|
61821
62262
|
buckets.formulas.push(row);
|
|
61822
62263
|
return;
|
|
61823
62264
|
}
|
|
62265
|
+
if (edge.edgeType === "references" && edge.source === "rollup-summary") {
|
|
62266
|
+
buckets.rollups.push(row);
|
|
62267
|
+
return;
|
|
62268
|
+
}
|
|
61824
62269
|
if (edge.edgeType === "references" && source.type === "EmailTemplate" && edge.properties["role"] === "body-merge") {
|
|
61825
62270
|
buckets.emails.push(row);
|
|
61826
62271
|
return;
|
|
@@ -61830,6 +62275,10 @@ var init_field_360 = __esm({
|
|
|
61830
62275
|
return;
|
|
61831
62276
|
}
|
|
61832
62277
|
if (edge.edgeType === "readsFrom") {
|
|
62278
|
+
if (source.type === "ConditionalContext") {
|
|
62279
|
+
buckets.automations.push(row);
|
|
62280
|
+
return;
|
|
62281
|
+
}
|
|
61833
62282
|
if (UI_NODE_TYPES.has(source.type) && CODE_NODE_TYPES.has(source.type)) {
|
|
61834
62283
|
buckets.ui.push(row);
|
|
61835
62284
|
return;
|
|
@@ -61907,6 +62356,7 @@ var init_field_360 = __esm({
|
|
|
61907
62356
|
}
|
|
61908
62357
|
const incoming = incomingResult.value;
|
|
61909
62358
|
const buckets = emptyBuckets();
|
|
62359
|
+
const resolvedIncoming = [];
|
|
61910
62360
|
for (const edge of incoming) {
|
|
61911
62361
|
if (edge.edgeType === "parentOf")
|
|
61912
62362
|
continue;
|
|
@@ -61915,7 +62365,13 @@ var init_field_360 = __esm({
|
|
|
61915
62365
|
return sr;
|
|
61916
62366
|
if (sr.value === null)
|
|
61917
62367
|
continue;
|
|
61918
|
-
|
|
62368
|
+
resolvedIncoming.push({ edge, source: sr.value });
|
|
62369
|
+
}
|
|
62370
|
+
const restated = indexRestatedConditionEdges(resolvedIncoming.map((r) => r.edge));
|
|
62371
|
+
for (const { edge, source } of resolvedIncoming) {
|
|
62372
|
+
if (restated.isRestatingCondition(edge))
|
|
62373
|
+
continue;
|
|
62374
|
+
classifyIncomingEdge(edge, source, buckets);
|
|
61919
62375
|
}
|
|
61920
62376
|
const parentObjectApi = fieldNode.parentId !== null && fieldNode.parentId.startsWith("CustomObject:") ? fieldNode.parentId.slice("CustomObject:".length) : fieldId.slice("CustomField:".length).split(".")[0] ?? "";
|
|
61921
62377
|
const supplementalWriters = await scanSupplementalFlowFieldWriters(ctx, parentObjectApi, fieldNode.apiName);
|
|
@@ -61936,6 +62392,11 @@ var init_field_360 = __esm({
|
|
|
61936
62392
|
}
|
|
61937
62393
|
const flowConditionScan = await scanFlowConditionFieldReaders(ctx, fieldId);
|
|
61938
62394
|
const readerIds = new Set(buckets.readers.map((r) => r.componentId));
|
|
62395
|
+
for (const row of buckets.automations) {
|
|
62396
|
+
const firer = row.properties?.["firerId"];
|
|
62397
|
+
if (typeof firer === "string")
|
|
62398
|
+
readerIds.add(firer);
|
|
62399
|
+
}
|
|
61939
62400
|
let flowConditionReaderCount = 0;
|
|
61940
62401
|
for (const fc of flowConditionScan.readers) {
|
|
61941
62402
|
if (readerIds.has(fc.flowId))
|
|
@@ -61986,6 +62447,7 @@ var init_field_360 = __esm({
|
|
|
61986
62447
|
const allBuckets = [
|
|
61987
62448
|
["validates", buckets.validates],
|
|
61988
62449
|
["formulas", buckets.formulas],
|
|
62450
|
+
["rollups", buckets.rollups],
|
|
61989
62451
|
["writers", buckets.writers],
|
|
61990
62452
|
["readers", buckets.readers],
|
|
61991
62453
|
["ui", buckets.ui],
|
|
@@ -62072,7 +62534,7 @@ var init_field_360 = __esm({
|
|
|
62072
62534
|
perSectionCounts[name] = rows.length;
|
|
62073
62535
|
}
|
|
62074
62536
|
const isPii = detectIsPii(fieldNode);
|
|
62075
|
-
const risk = computeRisk(perSectionCounts, isPii, isFormula, buckets.dependencies.length);
|
|
62537
|
+
const risk = computeRisk(perSectionCounts, isPii, isFormula, buckets.dependencies.length, restated.suppressedConditionCount);
|
|
62076
62538
|
const grantedByCount = incoming.filter((e) => e.edgeType === "grantedBy").length;
|
|
62077
62539
|
const summary = {
|
|
62078
62540
|
perSectionCounts,
|
|
@@ -62086,6 +62548,9 @@ var init_field_360 = __esm({
|
|
|
62086
62548
|
FIELD_360_Q165_DISCLOSURE,
|
|
62087
62549
|
"list view column AND filter field IDENTITY are composed into the `listViews` section (heuristic regex; a row's `referenceKind` is `fieldRef` for a column, `filterRef` for a filter predicate, or `columnAndFilter` for both) \u2014 but the saved view's runtime filter PREDICATE EVALUATION (whether a given record passes the filter) stays unmodeled and remains in dataNotAvailable as `list-view-filters`"
|
|
62088
62550
|
];
|
|
62551
|
+
if (restated.suppressedConditionCount > 0) {
|
|
62552
|
+
boundaries.push(`${restated.suppressedConditionCount} ConditionalContext \`readsFrom\` row(s) were FOLDED into \`validates\`: each restates a validation rule already listed there (the rule's errorConditionFormula is tokenized into BOTH a direct \`references\` edge and a condition edge, so counting both would report one rule as two referrers). Both edges remain in the graph \u2014 query them with sfi.get_edges \u2014 and the folded rows still count toward the automation risk axis, so \`riskLevel\` is unaffected. Conditions are listed but NOT evaluated: sfi does not know whether any record satisfies them.`);
|
|
62553
|
+
}
|
|
62089
62554
|
const analytics = reportDashboardUsageDetail(fieldNode);
|
|
62090
62555
|
const analyticsCoverage = summarizeCoverage(ctx.manifest, [
|
|
62091
62556
|
"Report",
|
|
@@ -62637,7 +63102,7 @@ ${comment}
|
|
|
62637
63102
|
|
|
62638
63103
|
// ../mcp/dist/src/tools/safe-to-delete-field.js
|
|
62639
63104
|
import { z as z54 } from "zod";
|
|
62640
|
-
var CUSTOM_FIELD_PREFIX9, EXAMPLES_PER_CATEGORY_LIMIT, CATEGORY_ORDER, safeToDeleteFieldInputSchema, classifyEdge, CATEGORY_NOTES, compareExamples, buildReasoning, promoteVerdict, aggregateVerdict, buildCoverageCaveat2, applyCoverageToVerdict2, buildPiiCompliance, VERDICT_ORDER, renderDeleteChecklist, buildSafeToDeleteFieldProposal, coreSafeToDeleteFieldHandler, safeToDeleteFieldHandler;
|
|
63105
|
+
var CUSTOM_FIELD_PREFIX9, EXAMPLES_PER_CATEGORY_LIMIT, CATEGORY_ORDER, safeToDeleteFieldInputSchema, classifyEdge, CATEGORY_NOTES, formatExampleCitation, compareExamples, buildReasoning, promoteVerdict, aggregateVerdict, buildCoverageCaveat2, applyCoverageToVerdict2, EDGE_FAMILIES_ADDED_IN, buildBuilderVersionCaveat, buildPiiCompliance, VERDICT_ORDER, renderDeleteChecklist, buildSafeToDeleteFieldProposal, coreSafeToDeleteFieldHandler, safeToDeleteFieldHandler;
|
|
62641
63106
|
var init_safe_to_delete_field = __esm({
|
|
62642
63107
|
"../mcp/dist/src/tools/safe-to-delete-field.js"() {
|
|
62643
63108
|
"use strict";
|
|
@@ -62656,15 +63121,18 @@ var init_safe_to_delete_field = __esm({
|
|
|
62656
63121
|
init_proposal_artifact();
|
|
62657
63122
|
init_report_dashboard_usage();
|
|
62658
63123
|
init_resolve_field_or_suggest();
|
|
63124
|
+
init_restated_condition_edges();
|
|
62659
63125
|
CUSTOM_FIELD_PREFIX9 = "CustomField:";
|
|
62660
63126
|
EXAMPLES_PER_CATEGORY_LIMIT = 5;
|
|
62661
63127
|
CATEGORY_ORDER = [
|
|
62662
63128
|
"apex",
|
|
62663
63129
|
"flow",
|
|
63130
|
+
"condition",
|
|
62664
63131
|
"workflow",
|
|
62665
63132
|
"validation",
|
|
62666
63133
|
"layout",
|
|
62667
63134
|
"formula",
|
|
63135
|
+
"rollup",
|
|
62668
63136
|
"integration",
|
|
62669
63137
|
"permission",
|
|
62670
63138
|
"sharing",
|
|
@@ -62702,10 +63170,16 @@ var init_safe_to_delete_field = __esm({
|
|
|
62702
63170
|
path: ["fieldId"]
|
|
62703
63171
|
});
|
|
62704
63172
|
classifyEdge = (edge, fromNode) => {
|
|
62705
|
-
|
|
63173
|
+
for (const rule2 of EDGE_SEMANTICS.bySource) {
|
|
63174
|
+
if (edge.edgeType !== rule2.edgeType)
|
|
63175
|
+
continue;
|
|
63176
|
+
if (edge.source !== rule2.source)
|
|
63177
|
+
continue;
|
|
63178
|
+
if (rule2.fromType !== void 0 && fromNode.type !== rule2.fromType)
|
|
63179
|
+
continue;
|
|
62706
63180
|
return {
|
|
62707
|
-
category:
|
|
62708
|
-
verdict:
|
|
63181
|
+
category: rule2.category,
|
|
63182
|
+
verdict: rule2.verdict
|
|
62709
63183
|
};
|
|
62710
63184
|
}
|
|
62711
63185
|
const rule = EDGE_SEMANTICS.byEdgeType[edge.edgeType];
|
|
@@ -62718,10 +63192,12 @@ var init_safe_to_delete_field = __esm({
|
|
|
62718
63192
|
CATEGORY_NOTES = Object.freeze({
|
|
62719
63193
|
apex: "Apex classes and triggers reference this field. Parsed-confidence matches (the default-on Apex AST pass \u2014 dot-access plus inline static SOQL SELECT/WHERE/ORDER BY/GROUP BY fields and constant-string Database.query literals) are real references; heuristic-confidence matches (apex-scanner regex fallback) may include false positives \u2014 spot-check those before deleting. String-BUILT dynamic SOQL remains invisible either way.",
|
|
62720
63194
|
flow: "Flow definitions read or write this field. The Flow XML names the field literally; deleting the field will break the Flow at runtime.",
|
|
63195
|
+
condition: "A condition EVALUATES this field. Seven firer families mint these: Flow entry criteria, Flow decisions, validation-rule conditions, workflow-rule criteria, and approval-process, assignment-rule, auto-response-rule and escalation-rule criteria. Each example carries the `firerId` of the component whose criteria it is, so the citation names the actual rule to go change rather than the family list (the example `id` itself is a synthetic ConditionalContext node). Salesforce refuses to delete a field a live condition tests, so this is a hard blocker even when the field appears on no layout and in no formula. The condition is listed but NOT evaluated: sfi does not know whether any record satisfies it.",
|
|
62721
63196
|
workflow: "A WorkflowRule field-update action writes this field. The action will fail at runtime if the field is removed.",
|
|
62722
|
-
validation:
|
|
63197
|
+
validation: 'A Validation Rule formula references this field. The Validation Rule will fail to compile if the field is removed, and Salesforce refuses the delete while the rule is live. The rule\u2019s `errorConditionFormula` is BOTH a formula reference and a condition that evaluates this field; those are two edges from one tokenized string, so the rule is counted ONCE here and the folded row is disclosed on the example as `alsoVia: ["condition"]` rather than reported as a second referrer. As with any condition, it is listed but NOT evaluated: sfi does not know whether any record satisfies it.',
|
|
62723
63198
|
layout: "This field is placed on one or more page layouts (deleting the field auto-removes it from them \u2014 Salesforce does not block the delete and the layouts keep working, but users of those layouts will no longer see the field) or referenced by a QuickAction (whose create/edit form is affected). Review the UI impact before deleting.",
|
|
62724
|
-
formula: "Another formula field references this field
|
|
63199
|
+
formula: "Another formula field references this field \u2014 either directly (tokenized from the formula body) or through a cross-object relationship traversal (`Parent__r.Field__c`) resolved against the org\u2019s lookup fields. Each EXAMPLE carries the referring field id, and a traversal-derived one also carries the `traversalPath` it was resolved from. The referencing formula will fail to compile if this field is removed.",
|
|
63200
|
+
rollup: "A roll-up summary field on the PARENT object depends on this field in one of three declared roles: `summarizedField` (the roll-up aggregates this field), `summaryForeignKey` (the roll-up is anchored on this master-detail field), or `summaryFilterItem` (the roll-up\u2019s filter tests this field). Each example carries its own `rollupRole`, so the citation names the coupling that actually exists rather than listing the possibilities. Salesforce REFUSES the delete outright while the roll-up exists \u2014 delete or repoint the roll-up first. The coupling is declared in the parent object\u2019s metadata, not this field\u2019s, so a search restricted to this object cannot find it.",
|
|
62725
63201
|
integration: "An integration surface (external data source, external service) references this field. Removing it may break the outbound or inbound contract.",
|
|
62726
63202
|
permission: "A Profile or Permission Set grants access to this field. Removing the field will drop the permission grant but is not blocked.",
|
|
62727
63203
|
sharing: "A SharingRule, Restriction Rule, or Scoping Rule criterion references this field. Deleting the field will break the rule.",
|
|
@@ -62730,6 +63206,21 @@ var init_safe_to_delete_field = __esm({
|
|
|
62730
63206
|
frontend: "A Lightning Web Component, Aura bundle, Visualforce page, or Visualforce component references this field. Heuristic-confidence matches (LWC/Aura scanners) may include false positives; spot-check the bundle source before deleting.",
|
|
62731
63207
|
unknown: "An incoming dependency edge was found whose source/type combination is not in the v2.0b classification table. Review the impact via sfi.get_impact before deleting."
|
|
62732
63208
|
});
|
|
63209
|
+
formatExampleCitation = (e) => {
|
|
63210
|
+
const qualifiers = [];
|
|
63211
|
+
if (e.rollupRole !== void 0)
|
|
63212
|
+
qualifiers.push(`as ${e.rollupRole}`);
|
|
63213
|
+
if (e.traversalPath !== void 0)
|
|
63214
|
+
qualifiers.push(`via ${e.traversalPath}`);
|
|
63215
|
+
if (e.firerId !== void 0)
|
|
63216
|
+
qualifiers.push(`fired by ${e.firerId}`);
|
|
63217
|
+
if (e.alsoVia !== void 0 && e.alsoVia.length > 0) {
|
|
63218
|
+
qualifiers.push(`also via ${[...e.alsoVia].join(", ")}`);
|
|
63219
|
+
}
|
|
63220
|
+
if (e.apiConfirmed === true)
|
|
63221
|
+
qualifiers.push("API-confirmed");
|
|
63222
|
+
return qualifiers.length === 0 ? e.id : `${e.id} (${qualifiers.join(", ")})`;
|
|
63223
|
+
};
|
|
62733
63224
|
compareExamples = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
62734
63225
|
buildReasoning = (buckets) => {
|
|
62735
63226
|
const out = [];
|
|
@@ -62796,6 +63287,19 @@ var init_safe_to_delete_field = __esm({
|
|
|
62796
63287
|
fireOnUnknownCoverage: true
|
|
62797
63288
|
});
|
|
62798
63289
|
applyCoverageToVerdict2 = (verdict, caveat) => applyCoverageToVerdict(verdict, caveat, "safe", "review");
|
|
63290
|
+
EDGE_FAMILIES_ADDED_IN = "0.3.0";
|
|
63291
|
+
buildBuilderVersionCaveat = (ctx) => {
|
|
63292
|
+
const runningVersion = process.env["SFI_PLUGIN_VERSION"];
|
|
63293
|
+
const builtByVersion = ctx.manifest.version;
|
|
63294
|
+
if (runningVersion === void 0 || runningVersion === "")
|
|
63295
|
+
return void 0;
|
|
63296
|
+
if (typeof builtByVersion !== "string" || builtByVersion === "") {
|
|
63297
|
+
return void 0;
|
|
63298
|
+
}
|
|
63299
|
+
if (!compareVersions(builtByVersion, runningVersion))
|
|
63300
|
+
return void 0;
|
|
63301
|
+
return `This vault was built by sf-intelligence ${builtByVersion}; you are running ${runningVersion}. Roll-up coupling, condition (Flow / validation-rule / workflow-rule / approval-process / assignment-rule / auto-response-rule / escalation-rule criteria) and resolved formula-traversal dependency edges were added in ${EDGE_FAMILIES_ADDED_IN} and are ABSENT until you re-run \`sfi refresh\` \u2014 a field whose only dependency is one of those cannot be seen here. A verdict of "safe" is therefore reported as "review" (NOT proven safe) on this vault.`;
|
|
63302
|
+
};
|
|
62799
63303
|
buildPiiCompliance = (node) => {
|
|
62800
63304
|
const { piiClassification, piiCategory } = detectPiiClassification(node);
|
|
62801
63305
|
if (!isRegulatedPiiClassification(piiClassification)) {
|
|
@@ -62822,6 +63326,9 @@ var init_safe_to_delete_field = __esm({
|
|
|
62822
63326
|
if (out.coverageCaveat !== void 0) {
|
|
62823
63327
|
lines.push(`> \u26A0\uFE0F **Coverage caveat (${out.coverageCaveat.status}):** ${out.coverageCaveat.message}`, "");
|
|
62824
63328
|
}
|
|
63329
|
+
if (out.builderVersionCaveat !== void 0) {
|
|
63330
|
+
lines.push(`> \u26A0\uFE0F **Stale vault:** ${out.builderVersionCaveat}`, "");
|
|
63331
|
+
}
|
|
62825
63332
|
lines.push(`**Verdict: ${out.verdict.toUpperCase()}**`, "");
|
|
62826
63333
|
const ordered = [...out.reasoning].sort((a, b) => VERDICT_ORDER[a.verdict] - VERDICT_ORDER[b.verdict]);
|
|
62827
63334
|
if (ordered.length === 0) {
|
|
@@ -62836,7 +63343,7 @@ var init_safe_to_delete_field = __esm({
|
|
|
62836
63343
|
r.category,
|
|
62837
63344
|
r.verdict,
|
|
62838
63345
|
r.count,
|
|
62839
|
-
r.examples.map(
|
|
63346
|
+
r.examples.map(formatExampleCitation).join("; ") || "\u2014"
|
|
62840
63347
|
])));
|
|
62841
63348
|
}
|
|
62842
63349
|
lines.push("", "_Proposed from the vault's last refresh \u2014 verify against your org before deleting. This never deploys or modifies the org._");
|
|
@@ -62850,7 +63357,7 @@ var init_safe_to_delete_field = __esm({
|
|
|
62850
63357
|
}));
|
|
62851
63358
|
}
|
|
62852
63359
|
reasons.push(...out.reasoning.map((r) => {
|
|
62853
|
-
const examples = r.examples.map(
|
|
63360
|
+
const examples = r.examples.map(formatExampleCitation).join("; ");
|
|
62854
63361
|
const apiTag = r.apiConfirmed === true ? ", API-confirmed" : "";
|
|
62855
63362
|
return `${r.category} (${r.verdict}, ${r.count}${apiTag})${examples ? `: ${examples}` : ""} \u2014 ${r.note}`;
|
|
62856
63363
|
}));
|
|
@@ -63011,6 +63518,7 @@ var init_safe_to_delete_field = __esm({
|
|
|
63011
63518
|
}
|
|
63012
63519
|
const buckets = /* @__PURE__ */ new Map();
|
|
63013
63520
|
let flsGrantCount = 0;
|
|
63521
|
+
const resolvedEdges = [];
|
|
63014
63522
|
for (const edge of edgesResult.value) {
|
|
63015
63523
|
if (edge.edgeType === "parentOf")
|
|
63016
63524
|
continue;
|
|
@@ -63029,13 +63537,31 @@ var init_safe_to_delete_field = __esm({
|
|
|
63029
63537
|
if (fromNode === null) {
|
|
63030
63538
|
continue;
|
|
63031
63539
|
}
|
|
63540
|
+
resolvedEdges.push({ edge, fromNode });
|
|
63541
|
+
}
|
|
63542
|
+
const restated = indexRestatedConditionEdges(resolvedEdges.map((r) => r.edge));
|
|
63543
|
+
for (const { edge, fromNode } of resolvedEdges) {
|
|
63544
|
+
if (restated.isRestatingCondition(edge))
|
|
63545
|
+
continue;
|
|
63032
63546
|
const { category, verdict: verdict2 } = classifyEdge(edge, fromNode);
|
|
63033
63547
|
const apiConfirmed = edge.properties["confirmedByApi"] === true;
|
|
63548
|
+
const edgeString = (key) => {
|
|
63549
|
+
const raw = edge.properties[key];
|
|
63550
|
+
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
63551
|
+
};
|
|
63552
|
+
const traversalPath = edgeString("traversalPath");
|
|
63553
|
+
const rollupRole = edgeString("rollupRole");
|
|
63554
|
+
const firerId = edgeString("firerId");
|
|
63555
|
+
const alsoVia = restated.isRestatedDirectReference(edge) ? ["condition"] : void 0;
|
|
63034
63556
|
const example = {
|
|
63035
63557
|
id: fromNode.id,
|
|
63036
63558
|
type: fromNode.type,
|
|
63037
63559
|
apiName: fromNode.apiName,
|
|
63038
|
-
...apiConfirmed ? { apiConfirmed: true } : {}
|
|
63560
|
+
...apiConfirmed ? { apiConfirmed: true } : {},
|
|
63561
|
+
...traversalPath !== void 0 ? { traversalPath } : {},
|
|
63562
|
+
...rollupRole !== void 0 ? { rollupRole } : {},
|
|
63563
|
+
...firerId !== void 0 ? { firerId } : {},
|
|
63564
|
+
...alsoVia !== void 0 ? { alsoVia } : {}
|
|
63039
63565
|
};
|
|
63040
63566
|
const existing = buckets.get(category);
|
|
63041
63567
|
if (existing === void 0) {
|
|
@@ -63077,7 +63603,9 @@ var init_safe_to_delete_field = __esm({
|
|
|
63077
63603
|
const reasoning = buildReasoning(buckets);
|
|
63078
63604
|
const coverageCaveat = buildCoverageCaveat2(ctx);
|
|
63079
63605
|
const piiCompliance = buildPiiCompliance(nodeResult.value);
|
|
63080
|
-
const
|
|
63606
|
+
const builderVersionCaveat = buildBuilderVersionCaveat(ctx);
|
|
63607
|
+
const coverageVerdict = applyCoverageToVerdict2(aggregateVerdict(reasoning), coverageCaveat);
|
|
63608
|
+
const staticVerdict = builderVersionCaveat !== void 0 && coverageVerdict === "safe" ? "review" : coverageVerdict;
|
|
63081
63609
|
const dataShape = await readFactBlock(ctx, fieldId, "fillRate");
|
|
63082
63610
|
const baseConfidence = reasoning.some((r) => r.verdict === "risky") ? "heuristic" : "declared";
|
|
63083
63611
|
const baseCompleteness = {
|
|
@@ -63087,6 +63615,10 @@ var init_safe_to_delete_field = __esm({
|
|
|
63087
63615
|
const baseLimitations = [
|
|
63088
63616
|
"Dependency evidence comes from the last offline vault refresh. String-built dynamic SOQL, reflective Apex, and runtime metadata access remain invisible to static analysis; inline static SOQL and constant-string Database.query field references ARE resolved (parsed-confidence Apex AST edges). A dot-access read/write to a shared Activity custom field through a Task or Event receiver (someTask.Field__c) is NOT a direct parsed edge on the Activity field \u2014 it is attached by a name-based polymorphic import alias (see next limitation).",
|
|
63089
63617
|
"Polymorphic Activity attribution: a shared Activity custom field can appear as up to three nodes (CustomField:Activity/Task/Event.<field>) that are ONE physical field. A read/write keyed on one representation is attached to the others at import by a name-based alias \u2014 re-pointed onto the Activity base when it exists, otherwise mirrored across the Task/Event describe-snapshot siblings (Task and Event share the custom fields defined on Activity). This is a heuristic name match applied at import, not a declared parent relationship \u2014 an admin should still confirm the referrer before deleting.",
|
|
63618
|
+
// Unconditional when it fires — a `blocking` verdict on a stale vault is
|
|
63619
|
+
// still a verdict computed from incomplete edge families, so the reader is
|
|
63620
|
+
// told even though the verdict did not move.
|
|
63621
|
+
...builderVersionCaveat !== void 0 ? [builderVersionCaveat] : [],
|
|
63090
63622
|
REPORT_DASHBOARD_USAGE_CAVEAT,
|
|
63091
63623
|
...flsGrantCount > 0 ? [
|
|
63092
63624
|
`${flsGrantCount} Profile/PermissionSet FLS grant(s) exist on this field (access, not usage) \u2014 excluded from the verdict; see \`sfi.field_access_audit\` or \`sfi.unused_fields_deep\`. Deleting the field drops grants automatically.`
|
|
@@ -63130,6 +63662,7 @@ var init_safe_to_delete_field = __esm({
|
|
|
63130
63662
|
...dataShape !== void 0 ? { dataShape } : {},
|
|
63131
63663
|
reasoning,
|
|
63132
63664
|
...coverageCaveat !== void 0 ? { coverageCaveat } : {},
|
|
63665
|
+
...builderVersionCaveat !== void 0 ? { builderVersionCaveat } : {},
|
|
63133
63666
|
...piiCompliance !== void 0 ? { piiCompliance } : {},
|
|
63134
63667
|
...flsGrantCount > 0 ? { flsGrantCount } : {},
|
|
63135
63668
|
...livePopulation !== void 0 ? { livePopulation } : {},
|
|
@@ -68795,7 +69328,7 @@ var init_fleet_find = __esm({
|
|
|
68795
69328
|
|
|
68796
69329
|
// ../mcp/dist/src/tools/generate-data-dictionary.js
|
|
68797
69330
|
import { z as z76 } from "zod";
|
|
68798
|
-
var Q125_FRESHNESS_DISCLOSURE, STRUCTURAL_DISCLOSURE, INHERITED_CONFIDENCE_DISCLOSURE, ERD_SCOPE_DISCLOSURE, CUSTOM_OBJECT_PREFIX6, generateDataDictionaryInputSchema,
|
|
69331
|
+
var Q125_FRESHNESS_DISCLOSURE, STRUCTURAL_DISCLOSURE, INHERITED_CONFIDENCE_DISCLOSURE, ERD_SCOPE_DISCLOSURE, CUSTOM_OBJECT_PREFIX6, generateDataDictionaryInputSchema, stringProp2, boolProp, escapeCell, renderFieldRow, compareByApiName, FIELDS_CSV_HEADER, csvRowForField, renderFieldsSection, renderRelationshipsSection, collectOutgoingErdRelationships, CUSTOM_FIELD_PREFIX18, parseCustomFieldId, renderErdSection, renderValidationRulesSection, renderPageLayoutsSection, renderTriggersAndFlowsSection, renderFooter, GENERATED_DOC_ENVELOPE_RESERVE_BYTES, GENERATED_DOC_BUDGET_FLOOR_BYTES, COMPONENT_IDS_SAMPLE_KEEP, GENERATED_DOC_MAX_RESPONSE_BYTES, GENERATED_DOC_RESPONSE_BUDGET_DEFAULT_BYTES, resolveResponseBudgetBytes, generatedDocByteBudget, byteLenOf, splitBodyIntoSections, sectionHeading, renderTruncationNote, fitDocumentToBudget, generateDataDictionaryHandler;
|
|
68799
69332
|
var init_generate_data_dictionary = __esm({
|
|
68800
69333
|
"../mcp/dist/src/tools/generate-data-dictionary.js"() {
|
|
68801
69334
|
"use strict";
|
|
@@ -68813,19 +69346,19 @@ var init_generate_data_dictionary = __esm({
|
|
|
68813
69346
|
objectId: z76.string().min(1),
|
|
68814
69347
|
format: z76.enum(["markdown", "csv"]).optional()
|
|
68815
69348
|
});
|
|
68816
|
-
|
|
69349
|
+
stringProp2 = (properties, key, fallback) => {
|
|
68817
69350
|
const v = properties[key];
|
|
68818
69351
|
return typeof v === "string" && v.length > 0 ? v : fallback;
|
|
68819
69352
|
};
|
|
68820
69353
|
boolProp = (properties, key) => properties[key] === true;
|
|
68821
69354
|
escapeCell = (raw) => raw.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
68822
69355
|
renderFieldRow = (field) => {
|
|
68823
|
-
const label = escapeCell(
|
|
69356
|
+
const label = escapeCell(stringProp2(field.properties, "label", field.label ?? field.apiName));
|
|
68824
69357
|
const apiName = escapeCell(field.apiName);
|
|
68825
|
-
const dataType =
|
|
68826
|
-
const isFormula =
|
|
69358
|
+
const dataType = stringProp2(field.properties, "dataType", "Unknown");
|
|
69359
|
+
const isFormula = stringProp2(field.properties, "formula", "") !== "";
|
|
68827
69360
|
const type = escapeCell(isFormula ? `${dataType} (formula)` : dataType);
|
|
68828
|
-
const description = escapeCell(
|
|
69361
|
+
const description = escapeCell(stringProp2(field.properties, "description", ""));
|
|
68829
69362
|
const required = boolProp(field.properties, "required") ? "yes" : "no";
|
|
68830
69363
|
return `| ${label} | \`${apiName}\` | ${type} | ${description} | ${required} |`;
|
|
68831
69364
|
};
|
|
@@ -68840,10 +69373,10 @@ var init_generate_data_dictionary = __esm({
|
|
|
68840
69373
|
"required"
|
|
68841
69374
|
];
|
|
68842
69375
|
csvRowForField = (objectApiName, field) => {
|
|
68843
|
-
const label =
|
|
68844
|
-
const dataType =
|
|
68845
|
-
const isFormula =
|
|
68846
|
-
const description =
|
|
69376
|
+
const label = stringProp2(field.properties, "label", field.label ?? field.apiName);
|
|
69377
|
+
const dataType = stringProp2(field.properties, "dataType", "Unknown");
|
|
69378
|
+
const isFormula = stringProp2(field.properties, "formula", "") !== "";
|
|
69379
|
+
const description = stringProp2(field.properties, "description", "");
|
|
68847
69380
|
const required = boolProp(field.properties, "required");
|
|
68848
69381
|
return [objectApiName, label, field.apiName, dataType, isFormula, description, required];
|
|
68849
69382
|
};
|
|
@@ -68863,10 +69396,10 @@ var init_generate_data_dictionary = __esm({
|
|
|
68863
69396
|
renderRelationshipsSection = (fields) => {
|
|
68864
69397
|
const rels = [];
|
|
68865
69398
|
for (const field of fields) {
|
|
68866
|
-
const dataType =
|
|
69399
|
+
const dataType = stringProp2(field.properties, "dataType", "");
|
|
68867
69400
|
if (dataType !== "Lookup" && dataType !== "MasterDetail")
|
|
68868
69401
|
continue;
|
|
68869
|
-
const referenceTo =
|
|
69402
|
+
const referenceTo = stringProp2(field.properties, "referenceTo", "Unknown");
|
|
68870
69403
|
rels.push({
|
|
68871
69404
|
fieldApiName: field.apiName,
|
|
68872
69405
|
relationshipType: dataType,
|
|
@@ -68889,7 +69422,7 @@ var init_generate_data_dictionary = __esm({
|
|
|
68889
69422
|
collectOutgoingErdRelationships = (objectApiName, fields) => {
|
|
68890
69423
|
const rels = [];
|
|
68891
69424
|
for (const field of fields) {
|
|
68892
|
-
const dataType =
|
|
69425
|
+
const dataType = stringProp2(field.properties, "dataType", "");
|
|
68893
69426
|
if (dataType !== "Lookup" && dataType !== "MasterDetail")
|
|
68894
69427
|
continue;
|
|
68895
69428
|
const referenceTo = field.properties["referenceTo"];
|
|
@@ -68931,7 +69464,7 @@ var init_generate_data_dictionary = __esm({
|
|
|
68931
69464
|
}
|
|
68932
69465
|
const sorted = [...rules].sort(compareByApiName);
|
|
68933
69466
|
const items = sorted.map((r) => {
|
|
68934
|
-
const description =
|
|
69467
|
+
const description = stringProp2(r.properties, "description", stringProp2(r.properties, "errorMessage", ""));
|
|
68935
69468
|
const desc = description.length > 0 ? ` \u2014 ${escapeCell(description)}` : "";
|
|
68936
69469
|
return `- \`${escapeCell(r.apiName)}\`${desc}`;
|
|
68937
69470
|
});
|
|
@@ -73819,7 +74352,7 @@ var init_role_hierarchy = __esm({
|
|
|
73819
74352
|
|
|
73820
74353
|
// ../mcp/dist/src/tools/generate-sharing-summary.js
|
|
73821
74354
|
import { z as z90 } from "zod";
|
|
73822
|
-
var OBJECT_SCAN_CAP, TYPE_SCAN_CAP2, UNMODELED_SHARING_DIMENSIONS_DISCLOSURE, generateSharingSummaryInputSchema, CUSTOM_OBJECT_ID_PREFIX, objectApiNameFromComponentId, escapeCell6,
|
|
74355
|
+
var OBJECT_SCAN_CAP, TYPE_SCAN_CAP2, UNMODELED_SHARING_DIMENSIONS_DISCLOSURE, generateSharingSummaryInputSchema, CUSTOM_OBJECT_ID_PREFIX, objectApiNameFromComponentId, escapeCell6, stringProp3, criteriaPredicate, PREDICATE_RULE_TYPES, ruleCriteriaCell, buildRecipientCell, renderObjectSection, renderRoleHierarchySection, sharingRuleObjectApiName, buildSharingRulesIndex, tallyGrants, generateSharingSummaryHandler;
|
|
73823
74356
|
var init_generate_sharing_summary = __esm({
|
|
73824
74357
|
"../mcp/dist/src/tools/generate-sharing-summary.js"() {
|
|
73825
74358
|
"use strict";
|
|
@@ -73850,7 +74383,7 @@ var init_generate_sharing_summary = __esm({
|
|
|
73850
74383
|
return componentId.startsWith(CUSTOM_OBJECT_ID_PREFIX) ? componentId.slice(CUSTOM_OBJECT_ID_PREFIX.length) : componentId;
|
|
73851
74384
|
};
|
|
73852
74385
|
escapeCell6 = (raw) => raw.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
73853
|
-
|
|
74386
|
+
stringProp3 = (properties, key, fallback) => {
|
|
73854
74387
|
const v = properties[key];
|
|
73855
74388
|
return typeof v === "string" && v.length > 0 ? v : fallback;
|
|
73856
74389
|
};
|
|
@@ -73938,8 +74471,8 @@ var init_generate_sharing_summary = __esm({
|
|
|
73938
74471
|
lines.push("| --- | --- | --- | --- | --- |");
|
|
73939
74472
|
const sorted = [...entry.sharingRules].sort((a, b) => a.apiName < b.apiName ? -1 : a.apiName > b.apiName ? 1 : 0);
|
|
73940
74473
|
for (const rule of sorted) {
|
|
73941
|
-
const ruleType =
|
|
73942
|
-
const access =
|
|
74474
|
+
const ruleType = stringProp3(rule.properties, "ruleType", "unknown");
|
|
74475
|
+
const access = stringProp3(rule.properties, "accessLevel", "Unknown");
|
|
73943
74476
|
const criteria = ruleCriteriaCell(ruleType, rule.properties);
|
|
73944
74477
|
const recipient = entry.ruleRecipients.get(rule.id) ?? "\u2014";
|
|
73945
74478
|
lines.push(`| \`${escapeCell6(rule.apiName)}\` | ${escapeCell6(ruleType)} | ${escapeCell6(recipient)} | ${escapeCell6(access)} | ${escapeCell6(criteria)} |`);
|
|
@@ -73953,9 +74486,9 @@ var init_generate_sharing_summary = __esm({
|
|
|
73953
74486
|
lines.push("| --- | --- | --- | --- | --- |");
|
|
73954
74487
|
const sortedRr = [...entry.restrictionRules].sort((a, b) => a.apiName < b.apiName ? -1 : a.apiName > b.apiName ? 1 : 0);
|
|
73955
74488
|
for (const rule of sortedRr) {
|
|
73956
|
-
const enforcement =
|
|
73957
|
-
const active =
|
|
73958
|
-
const recordFilter =
|
|
74489
|
+
const enforcement = stringProp3(rule.properties, "enforcementType", "Unknown");
|
|
74490
|
+
const active = stringProp3(rule.properties, "active", "unknown");
|
|
74491
|
+
const recordFilter = stringProp3(rule.properties, "recordFilter", "\u2014");
|
|
73959
74492
|
lines.push(`| \`${escapeCell6(rule.apiName)}\` | ${escapeCell6(rule.type)} | ${escapeCell6(enforcement)} | ${escapeCell6(active)} | ${escapeCell6(recordFilter)} |`);
|
|
73960
74493
|
}
|
|
73961
74494
|
}
|
|
@@ -73976,7 +74509,7 @@ var init_generate_sharing_summary = __esm({
|
|
|
73976
74509
|
lines.push(` ${safe}["${escapeCell6(role.apiName)}"]`);
|
|
73977
74510
|
}
|
|
73978
74511
|
for (const role of sorted) {
|
|
73979
|
-
const parent =
|
|
74512
|
+
const parent = stringProp3(role.properties, "parentRoleId", "");
|
|
73980
74513
|
if (parent.length === 0)
|
|
73981
74514
|
continue;
|
|
73982
74515
|
const safeChild = role.apiName.replace(/[^A-Za-z0-9_]/g, "_");
|
|
@@ -73995,7 +74528,7 @@ var init_generate_sharing_summary = __esm({
|
|
|
73995
74528
|
const dot = rule.apiName.indexOf(".");
|
|
73996
74529
|
if (dot > 0)
|
|
73997
74530
|
return rule.apiName.slice(0, dot);
|
|
73998
|
-
return
|
|
74531
|
+
return stringProp3(rule.properties, "sObjectType", "");
|
|
73999
74532
|
};
|
|
74000
74533
|
buildSharingRulesIndex = (rules) => {
|
|
74001
74534
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -74109,7 +74642,7 @@ var init_generate_sharing_summary = __esm({
|
|
|
74109
74642
|
const grantsResult = await tallyGrants(ctx, object);
|
|
74110
74643
|
if (!grantsResult.ok)
|
|
74111
74644
|
return err(grantsResult.error);
|
|
74112
|
-
const owd =
|
|
74645
|
+
const owd = stringProp3(object.properties, "sharingModel", "Unknown");
|
|
74113
74646
|
const externalOwdRaw = object.properties["externalSharingModel"];
|
|
74114
74647
|
const externalOwd = typeof externalOwdRaw === "string" && externalOwdRaw.length > 0 ? externalOwdRaw : null;
|
|
74115
74648
|
const rules = sharingIndex.get(object.apiName) ?? [];
|
|
@@ -74750,7 +75283,7 @@ var init_get_subgraph = __esm({
|
|
|
74750
75283
|
|
|
74751
75284
|
// ../mcp/dist/src/tools/guest-exposure-report.js
|
|
74752
75285
|
import { z as z94 } from "zod";
|
|
74753
|
-
var CUSTOM_SITE_PREFIX, NETWORK_PREFIX, PROFILE_PREFIX, OBJECT_PREFIX, FIELD_PREFIX, APEX_PREFIX2, SHARING_RULE_PREFIX, DEFAULT_LIMIT13, MAX_LIMIT14, FINDINGS_BYTE_BUDGET, SCAN_PAGE_SIZE3, guestExposureReportInputSchema, SEVERITY_RANK2, OBJECT_FLAGS3, flag,
|
|
75286
|
+
var CUSTOM_SITE_PREFIX, NETWORK_PREFIX, PROFILE_PREFIX, OBJECT_PREFIX, FIELD_PREFIX, APEX_PREFIX2, SHARING_RULE_PREFIX, DEFAULT_LIMIT13, MAX_LIMIT14, FINDINGS_BYTE_BUDGET, SCAN_PAGE_SIZE3, guestExposureReportInputSchema, SEVERITY_RANK2, OBJECT_FLAGS3, flag, stringProp4, boolProp2, objectOfFieldId2, findingObjectApiName, drainType, guestProfileIdForSite, guestExposureReportHandler;
|
|
74754
75287
|
var init_guest_exposure_report = __esm({
|
|
74755
75288
|
"../mcp/dist/src/tools/guest-exposure-report.js"() {
|
|
74756
75289
|
"use strict";
|
|
@@ -74831,7 +75364,7 @@ var init_guest_exposure_report = __esm({
|
|
|
74831
75364
|
"modifyAllRecords"
|
|
74832
75365
|
];
|
|
74833
75366
|
flag = (p, k) => p[k] === true;
|
|
74834
|
-
|
|
75367
|
+
stringProp4 = (p, k) => typeof p[k] === "string" && p[k].length > 0 ? p[k] : null;
|
|
74835
75368
|
boolProp2 = (p, k) => typeof p[k] === "boolean" ? p[k] : null;
|
|
74836
75369
|
objectOfFieldId2 = (fieldId) => {
|
|
74837
75370
|
if (!fieldId.startsWith(FIELD_PREFIX))
|
|
@@ -74867,8 +75400,8 @@ var init_guest_exposure_report = __esm({
|
|
|
74867
75400
|
return ok(all);
|
|
74868
75401
|
};
|
|
74869
75402
|
guestProfileIdForSite = (site) => {
|
|
74870
|
-
const declared =
|
|
74871
|
-
const label =
|
|
75403
|
+
const declared = stringProp4(site.properties, "guestProfileName");
|
|
75404
|
+
const label = stringProp4(site.properties, "masterLabel") ?? site.apiName;
|
|
74872
75405
|
return `${PROFILE_PREFIX}${declared ?? guestProfileNameForSite(label)}`;
|
|
74873
75406
|
};
|
|
74874
75407
|
guestExposureReportHandler = async (ctx, input2) => {
|
|
@@ -74990,7 +75523,7 @@ var init_guest_exposure_report = __esm({
|
|
|
74990
75523
|
}
|
|
74991
75524
|
const networkBySiteApiName = /* @__PURE__ */ new Map();
|
|
74992
75525
|
for (const net of allNetworks) {
|
|
74993
|
-
const siteName =
|
|
75526
|
+
const siteName = stringProp4(net.properties, "site");
|
|
74994
75527
|
if (siteName !== null)
|
|
74995
75528
|
networkBySiteApiName.set(siteName, net);
|
|
74996
75529
|
}
|
|
@@ -75000,7 +75533,7 @@ var init_guest_exposure_report = __esm({
|
|
|
75000
75533
|
scopedSites = allSites.filter((s) => s.id === communityId);
|
|
75001
75534
|
} else {
|
|
75002
75535
|
const net = allNetworks.find((n) => n.id === communityId);
|
|
75003
|
-
const siteName = net !== void 0 ?
|
|
75536
|
+
const siteName = net !== void 0 ? stringProp4(net.properties, "site") : null;
|
|
75004
75537
|
scopedSites = siteName !== null ? allSites.filter((s) => s.apiName === siteName) : [];
|
|
75005
75538
|
}
|
|
75006
75539
|
if (scopedSites.length === 0) {
|
|
@@ -75029,9 +75562,9 @@ var init_guest_exposure_report = __esm({
|
|
|
75029
75562
|
scanTruncated = true;
|
|
75030
75563
|
const guestRulesBySite = /* @__PURE__ */ new Map();
|
|
75031
75564
|
for (const rule of rulesResult.value) {
|
|
75032
|
-
if (
|
|
75565
|
+
if (stringProp4(rule.properties, "ruleType") !== "guest")
|
|
75033
75566
|
continue;
|
|
75034
|
-
const siteName =
|
|
75567
|
+
const siteName = stringProp4(rule.properties, "siteName");
|
|
75035
75568
|
if (siteName === null)
|
|
75036
75569
|
continue;
|
|
75037
75570
|
const bucket = guestRulesBySite.get(siteName) ?? [];
|
|
@@ -75045,7 +75578,7 @@ var init_guest_exposure_report = __esm({
|
|
|
75045
75578
|
for (const site of scopedSites) {
|
|
75046
75579
|
const network = networkBySiteApiName.get(site.apiName) ?? null;
|
|
75047
75580
|
const guestProfileId = guestProfileIdForSite(site);
|
|
75048
|
-
const siteLabel =
|
|
75581
|
+
const siteLabel = stringProp4(site.properties, "masterLabel") ?? site.apiName;
|
|
75049
75582
|
const profileResult = await getNodeById(ctx.graph, guestProfileId);
|
|
75050
75583
|
if (!profileResult.ok) {
|
|
75051
75584
|
return err({ kind: "internal", message: `graph query failed: ${profileResult.error.message}` });
|
|
@@ -75053,7 +75586,7 @@ var init_guest_exposure_report = __esm({
|
|
|
75053
75586
|
const guestProfileResolved = profileResult.value !== null;
|
|
75054
75587
|
if (guestProfileResolved)
|
|
75055
75588
|
guestProfilesResolved += 1;
|
|
75056
|
-
const experienceBundleId = network !== null &&
|
|
75589
|
+
const experienceBundleId = network !== null && stringProp4(network.properties, "picassoSite") !== null ? `ExperienceBundle:${stringProp4(network.properties, "picassoSite")}` : null;
|
|
75057
75590
|
const communityFindings = [];
|
|
75058
75591
|
if (guestProfileResolved) {
|
|
75059
75592
|
const edgesResult = await listEdges(ctx.graph, guestProfileId, {
|
|
@@ -75192,7 +75725,7 @@ var init_guest_exposure_report = __esm({
|
|
|
75192
75725
|
severity: "low",
|
|
75193
75726
|
nodeId: rule.id,
|
|
75194
75727
|
label: rule.apiName,
|
|
75195
|
-
detail: `guest sharing rule ${rule.id} (accessLevel ${
|
|
75728
|
+
detail: `guest sharing rule ${rule.id} (accessLevel ${stringProp4(rule.properties, "accessLevel") ?? "Read"}) shares records with the '${name}' site guest user \u2014 record-level applicability requires org data`,
|
|
75196
75729
|
grantConfidence: "declared",
|
|
75197
75730
|
guestLinkageConfidence: "heuristic"
|
|
75198
75731
|
});
|
|
@@ -75205,10 +75738,10 @@ var init_guest_exposure_report = __esm({
|
|
|
75205
75738
|
communities.push({
|
|
75206
75739
|
communityId: site.id,
|
|
75207
75740
|
label: siteLabel,
|
|
75208
|
-
siteType:
|
|
75741
|
+
siteType: stringProp4(site.properties, "siteType"),
|
|
75209
75742
|
active: boolProp2(site.properties, "active"),
|
|
75210
75743
|
networkId: network?.id ?? null,
|
|
75211
|
-
status: network !== null ?
|
|
75744
|
+
status: network !== null ? stringProp4(network.properties, "status") : null,
|
|
75212
75745
|
selfRegistration: network !== null ? boolProp2(network.properties, "selfRegistration") : null,
|
|
75213
75746
|
enableGuestFileAccess: network !== null ? boolProp2(network.properties, "enableGuestFileAccess") : null,
|
|
75214
75747
|
experienceBundleId,
|
|
@@ -75223,7 +75756,7 @@ var init_guest_exposure_report = __esm({
|
|
|
75223
75756
|
if (communityId === void 0) {
|
|
75224
75757
|
const modeledSiteApiNames = new Set(allSites.map((s) => s.apiName));
|
|
75225
75758
|
for (const net of allNetworks) {
|
|
75226
|
-
const siteName =
|
|
75759
|
+
const siteName = stringProp4(net.properties, "site");
|
|
75227
75760
|
if (siteName !== null && !modeledSiteApiNames.has(siteName))
|
|
75228
75761
|
orphanNetworks.push(net.id);
|
|
75229
75762
|
}
|
|
@@ -101960,7 +102493,7 @@ var init_group_membership = __esm({
|
|
|
101960
102493
|
|
|
101961
102494
|
// ../mcp/dist/src/tools/who-can-access-object.js
|
|
101962
102495
|
import { z as z162 } from "zod";
|
|
101963
|
-
var CUSTOM_OBJECT_PREFIX10, GROUP_MEMBER_PREFIX, DEFAULT_LIMIT25, MAX_LIMIT25, PUBLIC_OWD_READ, whoCanAccessObjectInputBaseSchema, whoCanAccessObjectInputSchema, BLIND_SPOTS, flag3,
|
|
102496
|
+
var CUSTOM_OBJECT_PREFIX10, GROUP_MEMBER_PREFIX, DEFAULT_LIMIT25, MAX_LIMIT25, PUBLIC_OWD_READ, whoCanAccessObjectInputBaseSchema, whoCanAccessObjectInputSchema, BLIND_SPOTS, flag3, stringProp5, sharingRuleObjectApiName2, ruleAccessToOp, whoCanAccessObjectHandler;
|
|
101964
102497
|
var init_who_can_access_object = __esm({
|
|
101965
102498
|
"../mcp/dist/src/tools/who-can-access-object.js"() {
|
|
101966
102499
|
"use strict";
|
|
@@ -102002,7 +102535,7 @@ var init_who_can_access_object = __esm({
|
|
|
102002
102535
|
"Manual sharing, Apex-managed sharing, account/opportunity/case teams, and sharing sets are record/runtime-level and are not modeled."
|
|
102003
102536
|
]);
|
|
102004
102537
|
flag3 = (p, k) => p[k] === true;
|
|
102005
|
-
|
|
102538
|
+
stringProp5 = (p, k) => typeof p[k] === "string" ? p[k] : "";
|
|
102006
102539
|
sharingRuleObjectApiName2 = (rule) => {
|
|
102007
102540
|
const parentId = rule.parentId;
|
|
102008
102541
|
if (typeof parentId === "string" && parentId.startsWith("CustomObject:")) {
|
|
@@ -102011,7 +102544,7 @@ var init_who_can_access_object = __esm({
|
|
|
102011
102544
|
const dot = rule.apiName.indexOf(".");
|
|
102012
102545
|
if (dot > 0)
|
|
102013
102546
|
return rule.apiName.slice(0, dot);
|
|
102014
|
-
return
|
|
102547
|
+
return stringProp5(rule.properties, "sObjectType");
|
|
102015
102548
|
};
|
|
102016
102549
|
ruleAccessToOp = (accessLevel) => accessLevel === "Edit" || accessLevel === "ReadWrite" ? "edit" : "read";
|
|
102017
102550
|
whoCanAccessObjectHandler = async (ctx, input2) => {
|
|
@@ -102036,9 +102569,9 @@ var init_who_can_access_object = __esm({
|
|
|
102036
102569
|
});
|
|
102037
102570
|
}
|
|
102038
102571
|
const objectNode = objectResult.value;
|
|
102039
|
-
const owd =
|
|
102572
|
+
const owd = stringProp5(objectNode.properties, "sharingModel") || "Unknown";
|
|
102040
102573
|
const owdGrantsAllInternalUsers = PUBLIC_OWD_READ.has(owd);
|
|
102041
|
-
const externalOwd =
|
|
102574
|
+
const externalOwd = stringProp5(objectNode.properties, "externalSharingModel") ?? null;
|
|
102042
102575
|
const restrictionRulesResult = await listNodesByType(ctx.graph, "RestrictionRule", {
|
|
102043
102576
|
limit: 500
|
|
102044
102577
|
});
|
|
@@ -102128,11 +102661,11 @@ var init_who_can_access_object = __esm({
|
|
|
102128
102661
|
for (const rule of rulesResult.value) {
|
|
102129
102662
|
if (sharingRuleObjectApiName2(rule) !== objectApiName)
|
|
102130
102663
|
continue;
|
|
102131
|
-
const ruleType =
|
|
102132
|
-
const accessLevel =
|
|
102664
|
+
const ruleType = stringProp5(rule.properties, "ruleType");
|
|
102665
|
+
const accessLevel = stringProp5(rule.properties, "accessLevel") || "Read";
|
|
102133
102666
|
const via = ruleType === "criteria" ? "criteria-sharing-rule" : ruleType === "guest" ? "guest-sharing-rule" : ruleType === "territory" || ruleType === "territoryGroup" ? "territory-sharing-rule" : "owner-sharing-rule";
|
|
102134
|
-
const predicate = ruleType === "owner" ? "" :
|
|
102135
|
-
const siteName = ruleType === "guest" ?
|
|
102667
|
+
const predicate = ruleType === "owner" ? "" : stringProp5(rule.properties, "booleanFilter");
|
|
102668
|
+
const siteName = ruleType === "guest" ? stringProp5(rule.properties, "siteName") : "";
|
|
102136
102669
|
const targetsResult = await listEdges(ctx.graph, rule.id, {
|
|
102137
102670
|
direction: "out",
|
|
102138
102671
|
edgeType: "sharedWith"
|
|
@@ -102401,7 +102934,7 @@ var init_who_can_run = __esm({
|
|
|
102401
102934
|
|
|
102402
102935
|
// ../mcp/dist/src/tools/why-cant-user-see-record.js
|
|
102403
102936
|
import { z as z164 } from "zod";
|
|
102404
|
-
var OWD_RESTRICTED, ACCESS_RANK, owdRank, grantSatisfiesObjectRead, grantSatisfiesObjectLevel, grantSatisfiesRecordVisible, systemPermsForLevel, ruleAccessSatisfiesLevel, ALL_INTERNAL_USERS_GROUP_ID, ROLE_HIERARCHY_MAX_DEPTH, whyCantUserSeeRecordInputSchema, LIVE_ACCESS_RESOLVER_NOTE, step2, evaluateOWD, evaluatePermissionGrants, noObjectFlags, SYSTEM_BYPASS_PERMS, objectFlagsSatisfyLevel, systemPermsSatisfyLevel, levelRelevantObjectFlags, computeMutedObjectAccess, evaluateSystemPermissions, walkRoleHierarchy, evaluateRoleHierarchy, buildUserMembership, fetchSharingRules, SHARED_TO_ROLE_PREFIX, SUBORDINATE_INHERITANCE, ownerRuleMatches, evaluateOwnerSharingRules, evaluateCriteriaSharingRules, TERRITORY_GUEST_RULE_TYPES, evaluateTerritoryAndGuestRules, listObjectChildRules, evaluateRestrictionRules, evaluateScopingRules, evaluatePermissionSetGroups, UNKNOWN_TAIL, aggregateVerdict8, CUSTOM_OBJECT_PREFIX11, PROFILE_PREFIX6, PERMISSION_SET_PREFIX5, PERMISSION_SET_GROUP_PREFIX2, ROLE_PREFIX2, GROUP_PREFIX2, QUEUE_PREFIX, coerceGroupId, coerceUserContext, evaluateModifyAllDataForCreate,
|
|
102937
|
+
var OWD_RESTRICTED, ACCESS_RANK, owdRank, grantSatisfiesObjectRead, grantSatisfiesObjectLevel, grantSatisfiesRecordVisible, systemPermsForLevel, ruleAccessSatisfiesLevel, ALL_INTERNAL_USERS_GROUP_ID, ROLE_HIERARCHY_MAX_DEPTH, whyCantUserSeeRecordInputSchema, LIVE_ACCESS_RESOLVER_NOTE, step2, evaluateOWD, evaluatePermissionGrants, noObjectFlags, SYSTEM_BYPASS_PERMS, objectFlagsSatisfyLevel, systemPermsSatisfyLevel, levelRelevantObjectFlags, computeMutedObjectAccess, evaluateSystemPermissions, walkRoleHierarchy, evaluateRoleHierarchy, buildUserMembership, fetchSharingRules, SHARED_TO_ROLE_PREFIX, SUBORDINATE_INHERITANCE, ownerRuleMatches, evaluateOwnerSharingRules, evaluateCriteriaSharingRules, TERRITORY_GUEST_RULE_TYPES, evaluateTerritoryAndGuestRules, listObjectChildRules, evaluateRestrictionRules, evaluateScopingRules, evaluatePermissionSetGroups, UNKNOWN_TAIL, aggregateVerdict8, CUSTOM_OBJECT_PREFIX11, PROFILE_PREFIX6, PERMISSION_SET_PREFIX5, PERMISSION_SET_GROUP_PREFIX2, ROLE_PREFIX2, GROUP_PREFIX2, QUEUE_PREFIX, coerceGroupId, coerceUserContext, evaluateModifyAllDataForCreate, objectApiNameOf2, evaluateRecordTypeForCreate, evaluateCreateAccess, whyCantUserSeeRecordHandler;
|
|
102405
102938
|
var init_why_cant_user_see_record = __esm({
|
|
102406
102939
|
"../mcp/dist/src/tools/why-cant-user-see-record.js"() {
|
|
102407
102940
|
"use strict";
|
|
@@ -103231,7 +103764,7 @@ var init_why_cant_user_see_record = __esm({
|
|
|
103231
103764
|
}
|
|
103232
103765
|
return ok(step2("SystemPermission", "visible", `Modify All Data grants create on every object: ${holders.join(", ")}`));
|
|
103233
103766
|
};
|
|
103234
|
-
|
|
103767
|
+
objectApiNameOf2 = (objectNode, componentId) => typeof objectNode.apiName === "string" && objectNode.apiName.length > 0 ? objectNode.apiName : componentId.replace(/^CustomObject:/, "");
|
|
103235
103768
|
evaluateRecordTypeForCreate = async (ctx, componentId, objectNode, userContext) => {
|
|
103236
103769
|
const edgesResult = await listEdges(ctx.graph, componentId, {
|
|
103237
103770
|
direction: "out",
|
|
@@ -103243,7 +103776,7 @@ var init_why_cant_user_see_record = __esm({
|
|
|
103243
103776
|
if (objectRecordTypes.length === 0) {
|
|
103244
103777
|
return ok(step2("RecordType", "visible", "object has no record types; create is not record-type-gated"));
|
|
103245
103778
|
}
|
|
103246
|
-
const objectApiName =
|
|
103779
|
+
const objectApiName = objectApiNameOf2(objectNode, componentId);
|
|
103247
103780
|
const granterIds = [];
|
|
103248
103781
|
if (userContext.profileId !== void 0)
|
|
103249
103782
|
granterIds.push(userContext.profileId);
|
|
@@ -103387,7 +103920,7 @@ var init_why_cant_user_see_record = __esm({
|
|
|
103387
103920
|
const createProfileOrPermSet = rawUserContext.profileId !== void 0 || rawUserContext.permissionSetIds !== void 0 && rawUserContext.permissionSetIds.length > 0;
|
|
103388
103921
|
let createNetAccess = null;
|
|
103389
103922
|
if (createProfileOrPermSet) {
|
|
103390
|
-
const objApi =
|
|
103923
|
+
const objApi = objectApiNameOf2(nodeResult.value, componentId);
|
|
103391
103924
|
const naResult = await computeMutedObjectAccess(ctx, componentId, objApi, rawUserContext, "create");
|
|
103392
103925
|
if (!naResult.ok) {
|
|
103393
103926
|
return err({ kind: "internal", message: `graph query failed: ${naResult.error}` });
|
|
@@ -103436,7 +103969,7 @@ var init_why_cant_user_see_record = __esm({
|
|
|
103436
103969
|
let mutedByForDeny = [];
|
|
103437
103970
|
let netAccess = null;
|
|
103438
103971
|
if (profileOrPermSetSupplied) {
|
|
103439
|
-
const objectApiName =
|
|
103972
|
+
const objectApiName = objectApiNameOf2(nodeResult.value, componentId);
|
|
103440
103973
|
const netAccessResult = await computeMutedObjectAccess(ctx, componentId, objectApiName, rawUserContext, level);
|
|
103441
103974
|
if (!netAccessResult.ok) {
|
|
103442
103975
|
return err({
|
|
@@ -104833,8 +105366,8 @@ var init_server = __esm({
|
|
|
104833
105366
|
init_tools();
|
|
104834
105367
|
SERVER_NAME = "sf-intelligence";
|
|
104835
105368
|
resolveServerVersion = () => {
|
|
104836
|
-
if ("0.2.
|
|
104837
|
-
return "0.2.
|
|
105369
|
+
if ("0.2.5") {
|
|
105370
|
+
return "0.2.5";
|
|
104838
105371
|
}
|
|
104839
105372
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
104840
105373
|
try {
|
|
@@ -105700,7 +106233,7 @@ var init_package_version = __esm({
|
|
|
105700
106233
|
"use strict";
|
|
105701
106234
|
readCliPackageVersion = () => {
|
|
105702
106235
|
if (true)
|
|
105703
|
-
return "0.2.
|
|
106236
|
+
return "0.2.5";
|
|
105704
106237
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
105705
106238
|
try {
|
|
105706
106239
|
const raw = readFileSync6(fileURLToPath4(new URL(rel, import.meta.url)), "utf8");
|
|
@@ -106648,7 +107181,7 @@ var init_setup_audit_trail = __esm({
|
|
|
106648
107181
|
// dist/src/source-reconcile.js
|
|
106649
107182
|
import { cp, readdir as readdir7, rm as rm3, stat as stat8 } from "node:fs/promises";
|
|
106650
107183
|
import { basename as basename16, join as join38, relative as relative3, sep as sep4 } from "node:path";
|
|
106651
|
-
var BUNDLE_PARENT_DIRS2, KNOWN_SIDECAR_SUFFIXES2, isKnownSidecar2, primaryRelPathForSidecar, walkSourceEntries,
|
|
107184
|
+
var BUNDLE_PARENT_DIRS2, KNOWN_SIDECAR_SUFFIXES2, isKnownSidecar2, primaryRelPathForSidecar, walkSourceEntries, DISPATCH_PROBE_ROOT, dispatchTailLength, strippedDxWrapper, comparisonKey, authoritativeIndex, RECONCILE_MAX_DELETE_FRACTION, RECONCILE_GUARD_MIN_CONSIDERED, layoutMismatchRefusal, reconcileSourceDeletions, syncAuthoritativeRetrieveIntoSource;
|
|
106652
107185
|
var init_source_reconcile = __esm({
|
|
106653
107186
|
"dist/src/source-reconcile.js"() {
|
|
106654
107187
|
"use strict";
|
|
@@ -106723,43 +107256,103 @@ var init_source_reconcile = __esm({
|
|
|
106723
107256
|
};
|
|
106724
107257
|
await walkDir2(sourceRoot);
|
|
106725
107258
|
};
|
|
106726
|
-
|
|
107259
|
+
DISPATCH_PROBE_ROOT = join38(sep4, "__sfi-reconcile-dispatch-probe__");
|
|
107260
|
+
dispatchTailLength = (segments, isDirectory) => {
|
|
107261
|
+
for (let length = 1; length <= segments.length; length += 1) {
|
|
107262
|
+
const tail = segments.slice(segments.length - length);
|
|
107263
|
+
const type = componentTypeFromSourcePath(DISPATCH_PROBE_ROOT, join38(DISPATCH_PROBE_ROOT, ...tail), isDirectory);
|
|
107264
|
+
if (type !== null)
|
|
107265
|
+
return length;
|
|
107266
|
+
}
|
|
107267
|
+
return null;
|
|
107268
|
+
};
|
|
107269
|
+
strippedDxWrapper = (segments) => {
|
|
107270
|
+
for (let i = 0; i <= 1 && i + 1 < segments.length; i += 1) {
|
|
107271
|
+
if (segments[i] === "main" && segments[i + 1] === "default")
|
|
107272
|
+
return segments.slice(i + 2);
|
|
107273
|
+
}
|
|
107274
|
+
return segments;
|
|
107275
|
+
};
|
|
107276
|
+
comparisonKey = (relPath, isDirectory) => {
|
|
107277
|
+
const segments = relPath.split(sep4);
|
|
107278
|
+
const fileName = segments[segments.length - 1] ?? relPath;
|
|
107279
|
+
const primaryRel = primaryRelPathForSidecar(relPath, fileName);
|
|
107280
|
+
const probeSegments = primaryRel === null ? segments : primaryRel.split(sep4);
|
|
107281
|
+
const length = dispatchTailLength(probeSegments, isDirectory);
|
|
107282
|
+
const tail = length === null ? strippedDxWrapper(segments) : segments.slice(segments.length - length);
|
|
107283
|
+
return tail.join("/");
|
|
107284
|
+
};
|
|
107285
|
+
authoritativeIndex = async (authoritativeRoot, typesToReconcile) => {
|
|
106727
107286
|
const entries = [];
|
|
106728
107287
|
await walkSourceEntries(authoritativeRoot, entries);
|
|
106729
|
-
|
|
107288
|
+
const keys = /* @__PURE__ */ new Set();
|
|
107289
|
+
let inScopeCount = 0;
|
|
107290
|
+
for (const entry of entries) {
|
|
107291
|
+
keys.add(comparisonKey(entry.relPath, entry.isDirectory));
|
|
107292
|
+
if (typesToReconcile.has(entry.type))
|
|
107293
|
+
inScopeCount += 1;
|
|
107294
|
+
}
|
|
107295
|
+
return { keys, inScopeCount };
|
|
107296
|
+
};
|
|
107297
|
+
RECONCILE_MAX_DELETE_FRACTION = 0.5;
|
|
107298
|
+
RECONCILE_GUARD_MIN_CONSIDERED = 20;
|
|
107299
|
+
layoutMismatchRefusal = (deleteCount, consideredCount, authoritative) => {
|
|
107300
|
+
if (deleteCount === 0 || consideredCount === 0)
|
|
107301
|
+
return null;
|
|
107302
|
+
const reason = (detail) => `Refusing to reconcile source deletions: ${detail}. This is the signature of a source-layout mismatch between the vault tree and the authoritative retrieve output, not of components deleted in the org \u2014 a reconcile that misreads the layout finds nothing in common and proposes to delete everything. The vault was left untouched; re-run the refresh once the layouts agree, or delete the stale files deliberately if the org really did lose them.`;
|
|
107303
|
+
if (deleteCount === consideredCount && authoritative.inScopeCount > 0) {
|
|
107304
|
+
return reason(`all ${consideredCount} in-scope vault file(s) would be deleted even though the retrieve returned ${authoritative.inScopeCount} component file(s) of those same types`);
|
|
107305
|
+
}
|
|
107306
|
+
if (consideredCount >= RECONCILE_GUARD_MIN_CONSIDERED && deleteCount > consideredCount * RECONCILE_MAX_DELETE_FRACTION) {
|
|
107307
|
+
return reason(`${deleteCount} of ${consideredCount} in-scope vault file(s) would be deleted, over the ${Math.round(RECONCILE_MAX_DELETE_FRACTION * 100)}% ceiling for a single refresh`);
|
|
107308
|
+
}
|
|
107309
|
+
return null;
|
|
106730
107310
|
};
|
|
106731
107311
|
reconcileSourceDeletions = async (sourceDir, authoritativeDir, typesToReconcile) => {
|
|
106732
107312
|
if (typesToReconcile.size === 0) {
|
|
106733
|
-
return { deletedPaths: [], deletedCount: 0 };
|
|
107313
|
+
return { deletedPaths: [], deletedCount: 0, consideredCount: 0, refused: false };
|
|
106734
107314
|
}
|
|
106735
|
-
const authoritative = await
|
|
107315
|
+
const authoritative = await authoritativeIndex(authoritativeDir, typesToReconcile);
|
|
106736
107316
|
const sourceEntries = [];
|
|
106737
107317
|
await walkSourceEntries(sourceDir, sourceEntries);
|
|
106738
|
-
const
|
|
106739
|
-
const
|
|
106740
|
-
|
|
106741
|
-
|
|
106742
|
-
continue;
|
|
107318
|
+
const considered = sourceEntries.filter((entry) => typesToReconcile.has(entry.type));
|
|
107319
|
+
const candidates = [];
|
|
107320
|
+
const candidateBundleDirs = /* @__PURE__ */ new Set();
|
|
107321
|
+
for (const entry of considered) {
|
|
106743
107322
|
const fileName = basename16(entry.absPath);
|
|
106744
107323
|
const primaryRel = primaryRelPathForSidecar(entry.relPath, fileName);
|
|
106745
|
-
const
|
|
106746
|
-
|
|
107324
|
+
const ownKey = comparisonKey(entry.relPath, entry.isDirectory);
|
|
107325
|
+
const primaryKey = primaryRel === null ? null : comparisonKey(primaryRel, entry.isDirectory);
|
|
107326
|
+
if (authoritative.keys.has(ownKey))
|
|
106747
107327
|
continue;
|
|
106748
|
-
if (
|
|
106749
|
-
deletedBundleDirs.add(entry.absPath);
|
|
106750
|
-
deletedPaths.push(entry.relPath);
|
|
107328
|
+
if (primaryKey !== null && authoritative.keys.has(primaryKey))
|
|
106751
107329
|
continue;
|
|
106752
|
-
|
|
106753
|
-
|
|
106754
|
-
|
|
106755
|
-
continue;
|
|
106756
|
-
await rm3(entry.absPath, { force: true });
|
|
106757
|
-
deletedPaths.push(entry.relPath);
|
|
107330
|
+
if (entry.isDirectory)
|
|
107331
|
+
candidateBundleDirs.add(entry.absPath);
|
|
107332
|
+
candidates.push(entry);
|
|
106758
107333
|
}
|
|
106759
|
-
|
|
106760
|
-
|
|
107334
|
+
const doomed = candidates.filter((entry) => entry.isDirectory || ![...candidateBundleDirs].some((dir) => entry.absPath.startsWith(`${dir}${sep4}`)));
|
|
107335
|
+
const refusalReason = layoutMismatchRefusal(doomed.length, considered.length, authoritative);
|
|
107336
|
+
if (refusalReason !== null) {
|
|
107337
|
+
return {
|
|
107338
|
+
deletedPaths: [],
|
|
107339
|
+
deletedCount: 0,
|
|
107340
|
+
consideredCount: considered.length,
|
|
107341
|
+
refused: true,
|
|
107342
|
+
refusalReason
|
|
107343
|
+
};
|
|
107344
|
+
}
|
|
107345
|
+
const deletedPaths = [];
|
|
107346
|
+
for (const entry of doomed) {
|
|
107347
|
+
await rm3(entry.absPath, { recursive: entry.isDirectory, force: true });
|
|
107348
|
+
deletedPaths.push(entry.relPath);
|
|
106761
107349
|
}
|
|
106762
|
-
return {
|
|
107350
|
+
return {
|
|
107351
|
+
deletedPaths,
|
|
107352
|
+
deletedCount: deletedPaths.length,
|
|
107353
|
+
consideredCount: considered.length,
|
|
107354
|
+
refused: false
|
|
107355
|
+
};
|
|
106763
107356
|
};
|
|
106764
107357
|
syncAuthoritativeRetrieveIntoSource = async (sourceDir, authoritativeDir) => {
|
|
106765
107358
|
try {
|
|
@@ -107934,19 +108527,26 @@ var init_staged_refresh = __esm({
|
|
|
107934
108527
|
// dist/src/commands/refresh.js
|
|
107935
108528
|
var refresh_exports = {};
|
|
107936
108529
|
__export(refresh_exports, {
|
|
108530
|
+
FOLD_ERASED_COVERAGE_TYPES: () => FOLD_ERASED_COVERAGE_TYPES,
|
|
107937
108531
|
PROFILE_COBATCH_GROUP: () => PROFILE_COBATCH_GROUP,
|
|
107938
108532
|
PROFILE_GRANT_DISCLOSURE: () => PROFILE_GRANT_DISCLOSURE,
|
|
108533
|
+
REPORT_PULL_DISCLOSURE: () => REPORT_PULL_DISCLOSURE,
|
|
108534
|
+
REPORT_RETRIEVE_BATCH_SIZE: () => REPORT_RETRIEVE_BATCH_SIZE,
|
|
107939
108535
|
appendStandardObjectDescribeFields: () => appendStandardObjectDescribeFields,
|
|
107940
108536
|
assessProfileGrantIntegrity: () => assessProfileGrantIntegrity,
|
|
108537
|
+
buildCoverageEntries: () => buildCoverageEntries2,
|
|
107941
108538
|
buildFolderedReportManifest: () => buildFolderedReportManifest,
|
|
107942
108539
|
buildPackageXml: () => buildPackageXml,
|
|
107943
108540
|
buildRefreshPulse: () => buildRefreshPulse,
|
|
108541
|
+
chunkFolderedReportManifest: () => chunkFolderedReportManifest,
|
|
107944
108542
|
classifyForDemandRetrieve: () => classifyForDemandRetrieve,
|
|
107945
108543
|
classifyRetrieveError: () => classifyRetrieveError,
|
|
107946
108544
|
computeChangeSummary: () => computeChangeSummary,
|
|
107947
108545
|
computeProfileGrantStats: () => computeProfileGrantStats,
|
|
107948
108546
|
countLandedReportMembers: () => countLandedReportMembers,
|
|
107949
108547
|
decorateProfileGrantCoverage: () => decorateProfileGrantCoverage,
|
|
108548
|
+
decorateReportPullCoverage: () => decorateReportPullCoverage,
|
|
108549
|
+
decorateReportsCapCoverage: () => decorateReportsCapCoverage,
|
|
107950
108550
|
formatDemandRetrieveSummary: () => formatDemandRetrieveSummary,
|
|
107951
108551
|
formatRefreshSummary: () => formatRefreshSummary,
|
|
107952
108552
|
formatReportsCapSummary: () => formatReportsCapSummary,
|
|
@@ -107971,7 +108571,7 @@ __export(refresh_exports, {
|
|
|
107971
108571
|
import { appendFile as appendFile6, mkdir as mkdir15, readdir as readdir9, readFile as readFile109, rename as rename6, rm as rm5, stat as stat10, writeFile as writeFile14 } from "node:fs/promises";
|
|
107972
108572
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
107973
108573
|
import { join as join43, resolve as resolve8 } from "node:path";
|
|
107974
|
-
var SF_API_VERSION3, EMPTY_COUNTS, EMPTY_SKIPPED, failed, PACKAGE_VERSION, extractCachePath, loadExtractCache, saveExtractCache, applyApexAstEdges, runRefresh, preserveFactsForSideBuild, aggregateFailuresByType, computeProfileGrantStats, PROFILE_GRANT_DISCLOSURE, PRIOR_GRANTED_BY_FLOOR, assessProfileGrantIntegrity, decorateProfileGrantCoverage, buildCoverageEntries2, decoratePendingCoverage, decorateReportsCapCoverage, countGraphTotals, fullRebuild, importGraph, persistResolveIndexBestEffort, runWithOpenGraph, appendRefreshHistory, appendRiskScores, writeOnboardingDoc, writeOrgCard, diffCounts, sumCounts, metricCounts, signedDelta, findAnnotationOrphans, buildRefreshPulse, computeChangeSummary, TOOLING_API_ENRICHED_TYPES, ENRICH_CANDIDATE_PAGE_SIZE, runToolingApiEnrichment, loadVaultConfig, METADATA_API_NAME2, toApiName, SF_MAX_BUFFER, SF_RETRIEVE_TIMEOUT_MS, SF_QUERY_TIMEOUT_MS, runSf, STANDARD_OBJECTS_TO_MODEL, manifestMembersForType, appendStandardObjectDescribeFields, AUTOMATION_EDGE_TYPES, objectsToExpandManifest, buildPackageXml, getOrgSupportedTypes, getOrgObjectNames, selectManifestTypes, classifyRetrieveError, PROFILE_COBATCH_GROUP, NO_ATOMIC_GROUP, toAtomicUnits, splitTypeBatch, salientErrorLine, summarizeRetrieveFailures, retrieveWithFallback, retrieveTypeBatch, runSfRetrieve, computeReconciledTypes, retrieveAdditiveManifest, runSfRetrieveObjects, buildFolderedReportManifest, REPORT_META_SUFFIXES, REPORT_DIR_MARKERS, countLandedReportMembers, collectReportMetaFiles, runSfRetrieveFolderedReports, reportsCap, runSfRetrieveSmartReports, formatFailure, formatBlock, formatChangeSummary, formatRefreshSummary, formatReportsCapSummary, SKIPPED_TOP_N, KNOWN_UNMODELED_SKIP_DIRS, formatSkippedWarning, formatToolingApiSummary, demandCoverageStatusOf, DEMAND_REFUSAL_REASON, classifyForDemandRetrieve, markDemandQueueDrains, runDemandRetrieve, formatDemandRetrieveSummary, registerRefreshCommand;
|
|
108574
|
+
var SF_API_VERSION3, REPORT_PULL_DISCLOSURE, EMPTY_COUNTS, EMPTY_SKIPPED, failed, PACKAGE_VERSION, extractCachePath, loadExtractCache, saveExtractCache, applyApexAstEdges, runRefresh, preserveFactsForSideBuild, aggregateFailuresByType, computeProfileGrantStats, PROFILE_GRANT_DISCLOSURE, PRIOR_GRANTED_BY_FLOOR, assessProfileGrantIntegrity, decorateProfileGrantCoverage, FOLD_ERASED_COVERAGE_TYPES, buildCoverageEntries2, decoratePendingCoverage, decorateReportsCapCoverage, decorateReportPullCoverage, countGraphTotals, fullRebuild, importGraph, persistResolveIndexBestEffort, runWithOpenGraph, appendRefreshHistory, appendRiskScores, writeOnboardingDoc, writeOrgCard, diffCounts, sumCounts, metricCounts, signedDelta, findAnnotationOrphans, buildRefreshPulse, computeChangeSummary, TOOLING_API_ENRICHED_TYPES, ENRICH_CANDIDATE_PAGE_SIZE, runToolingApiEnrichment, loadVaultConfig, METADATA_API_NAME2, toApiName, SF_MAX_BUFFER, SF_RETRIEVE_TIMEOUT_MS, SF_QUERY_TIMEOUT_MS, runSf, STANDARD_OBJECTS_TO_MODEL, manifestMembersForType, appendStandardObjectDescribeFields, AUTOMATION_EDGE_TYPES, objectsToExpandManifest, buildPackageXml, getOrgSupportedTypes, getOrgObjectNames, selectManifestTypes, classifyRetrieveError, PROFILE_COBATCH_GROUP, NO_ATOMIC_GROUP, toAtomicUnits, splitTypeBatch, salientErrorLine, summarizeRetrieveFailures, retrieveWithFallback, retrieveTypeBatch, runSfRetrieve, computeReconciledTypes, retrieveAdditiveManifest, runSfRetrieveObjects, buildFolderedReportManifest, REPORT_RETRIEVE_BATCH_SIZE, folderedReportPackageXml, chunkFolderedReportManifest, retrieveFolderedReportBatches, REPORT_META_SUFFIXES, REPORT_DIR_MARKERS, countLandedReportMembers, collectReportMetaFiles, runSfRetrieveFolderedReports, reportsCap, runSfRetrieveSmartReports, formatFailure, formatBlock, formatChangeSummary, formatRefreshSummary, formatReportsCapSummary, SKIPPED_TOP_N, KNOWN_UNMODELED_SKIP_DIRS, formatSkippedWarning, formatToolingApiSummary, demandCoverageStatusOf, DEMAND_REFUSAL_REASON, classifyForDemandRetrieve, markDemandQueueDrains, runDemandRetrieve, formatDemandRetrieveSummary, registerRefreshCommand;
|
|
107975
108575
|
var init_refresh = __esm({
|
|
107976
108576
|
"dist/src/commands/refresh.js"() {
|
|
107977
108577
|
"use strict";
|
|
@@ -107994,6 +108594,7 @@ var init_refresh = __esm({
|
|
|
107994
108594
|
init_refresh_preflight();
|
|
107995
108595
|
init_snapshot2();
|
|
107996
108596
|
SF_API_VERSION3 = "62.0";
|
|
108597
|
+
REPORT_PULL_DISCLOSURE = "report/dashboard pull did not complete \u2014 Report/Dashboard coverage is UNPROVEN, not empty";
|
|
107997
108598
|
EMPTY_COUNTS = { components: {}, edges: {} };
|
|
107998
108599
|
EMPTY_SKIPPED = Object.freeze({});
|
|
107999
108600
|
failed = (startedMs, fatalError, errors, counts = EMPTY_COUNTS, skippedDirectories = EMPTY_SKIPPED) => ({
|
|
@@ -108205,6 +108806,7 @@ var init_refresh = __esm({
|
|
|
108205
108806
|
let pullManifestTypes = null;
|
|
108206
108807
|
let confirmedTypes = null;
|
|
108207
108808
|
let sourceReconcileDeleted = 0;
|
|
108809
|
+
let reconcileRefusals = [];
|
|
108208
108810
|
let retrieveFailures = [];
|
|
108209
108811
|
if (!opts.noPull) {
|
|
108210
108812
|
const priorManifest = await loadManifest(configResult.value.vaultRoot);
|
|
@@ -108227,6 +108829,18 @@ var init_refresh = __esm({
|
|
|
108227
108829
|
if (sourceReconcileDeleted > 0) {
|
|
108228
108830
|
progress(`Reconciled source: removed ${sourceReconcileDeleted} stale file(s) deleted in the org.`);
|
|
108229
108831
|
}
|
|
108832
|
+
reconcileRefusals = pulled.value.reconcileRefusals ?? [];
|
|
108833
|
+
if (reconcileRefusals.length > 0) {
|
|
108834
|
+
process.stdout.write(`
|
|
108835
|
+
SOURCE RECONCILE REFUSED (${reconcileRefusals.length}) \u2014 stale files were KEPT, not deleted.
|
|
108836
|
+
This is the layout-mismatch guard: a deletion set that large is far more likely to mean the
|
|
108837
|
+
retrieve layout changed than that the org really dropped that much metadata. The vault is
|
|
108838
|
+
SAFE but may now carry entries for components that no longer exist.
|
|
108839
|
+
` + reconcileRefusals.map((r) => ` - ${r}
|
|
108840
|
+
`).join("") + `If the org genuinely purged this much, re-run after confirming; otherwise this is a bug.
|
|
108841
|
+
|
|
108842
|
+
`);
|
|
108843
|
+
}
|
|
108230
108844
|
if (retrieveFailures.length > 0) {
|
|
108231
108845
|
progress(`Partial retrieve: ${retrieveFailures.length} metadata type(s) failed and were skipped \u2014 the vault is built from what landed. ${summarizeRetrieveFailures(retrieveFailures)}`);
|
|
108232
108846
|
}
|
|
@@ -108250,14 +108864,32 @@ var init_refresh = __esm({
|
|
|
108250
108864
|
}
|
|
108251
108865
|
}
|
|
108252
108866
|
let reportsCapStats;
|
|
108867
|
+
let reportPull;
|
|
108253
108868
|
if (opts.withReports === true && !opts.noPull) {
|
|
108254
108869
|
progress("Pulling ALL folder-based Reports / Dashboards (--with-reports)...");
|
|
108255
108870
|
const pulledReports = await runSfRetrieveFolderedReports(targetOrg, paths.source);
|
|
108256
108871
|
if (!pulledReports.ok) {
|
|
108257
|
-
|
|
108258
|
-
|
|
108259
|
-
|
|
108260
|
-
|
|
108872
|
+
reportPull = {
|
|
108873
|
+
mode: "full",
|
|
108874
|
+
outcome: "failed",
|
|
108875
|
+
error: pulledReports.error,
|
|
108876
|
+
attemptedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
108877
|
+
};
|
|
108878
|
+
progress(`--with-reports pull FAILED (non-fatal, recorded): ${pulledReports.error}`);
|
|
108879
|
+
} else {
|
|
108880
|
+
if (pulledReports.value.batchErrors.length > 0) {
|
|
108881
|
+
reportPull = {
|
|
108882
|
+
mode: "full",
|
|
108883
|
+
outcome: "partial",
|
|
108884
|
+
error: pulledReports.value.batchErrors.join("; "),
|
|
108885
|
+
attemptedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
108886
|
+
};
|
|
108887
|
+
progress(`--with-reports pull lost ${pulledReports.value.batchErrors.length} batch(es) (non-fatal, recorded): ${pulledReports.value.batchErrors[0] ?? ""}`);
|
|
108888
|
+
}
|
|
108889
|
+
if (pulledReports.value.reports + pulledReports.value.dashboards > 0) {
|
|
108890
|
+
progress(`Requested ${pulledReports.value.reports} report(s) + ${pulledReports.value.dashboards} dashboard(s); re-extracting...`);
|
|
108891
|
+
walked = await walkAndExtract(paths.source, requestedTypes, prevCache);
|
|
108892
|
+
}
|
|
108261
108893
|
}
|
|
108262
108894
|
} else if (opts.withReports === void 0 && !opts.noPull && requestedTypes === null) {
|
|
108263
108895
|
const cap2 = reportsCap();
|
|
@@ -108265,8 +108897,23 @@ var init_refresh = __esm({
|
|
|
108265
108897
|
progress(`Pulling top ${cap2} Reports / Dashboards by usage (default; --no-reports to skip)...`);
|
|
108266
108898
|
const smart = await runSfRetrieveSmartReports(targetOrg, paths.source, cap2);
|
|
108267
108899
|
if (!smart.ok) {
|
|
108268
|
-
|
|
108900
|
+
reportPull = {
|
|
108901
|
+
mode: "smart",
|
|
108902
|
+
outcome: "failed",
|
|
108903
|
+
error: smart.error,
|
|
108904
|
+
attemptedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
108905
|
+
};
|
|
108906
|
+
progress(`smart report pull FAILED (non-fatal, recorded): ${smart.error}`);
|
|
108269
108907
|
} else {
|
|
108908
|
+
if (smart.value.batchErrors.length > 0) {
|
|
108909
|
+
reportPull = {
|
|
108910
|
+
mode: "smart",
|
|
108911
|
+
outcome: "partial",
|
|
108912
|
+
error: smart.value.batchErrors.join("; "),
|
|
108913
|
+
attemptedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
108914
|
+
};
|
|
108915
|
+
progress(`smart report pull lost ${smart.value.batchErrors.length} batch(es) (non-fatal, recorded): ${smart.value.batchErrors[0] ?? ""}`);
|
|
108916
|
+
}
|
|
108270
108917
|
reportsCapStats = {
|
|
108271
108918
|
reports: { total: smart.value.totals.reports, requested: smart.value.reports, retrieved: smart.value.landed.reports },
|
|
108272
108919
|
dashboards: { total: smart.value.totals.dashboards, requested: smart.value.dashboards, retrieved: smart.value.landed.dashboards }
|
|
@@ -108331,7 +108978,8 @@ var init_refresh = __esm({
|
|
|
108331
108978
|
retrieveFailures,
|
|
108332
108979
|
...reconciledTypes !== null ? { reconciledTypes } : {},
|
|
108333
108980
|
...apexAstStats !== void 0 ? { apexAstStats } : {},
|
|
108334
|
-
...reportsCapStats !== void 0 ? { reportsCapStats } : {}
|
|
108981
|
+
...reportsCapStats !== void 0 ? { reportsCapStats } : {},
|
|
108982
|
+
...reportPull !== void 0 ? { reportPull } : {}
|
|
108335
108983
|
};
|
|
108336
108984
|
if (!renameOver) {
|
|
108337
108985
|
try {
|
|
@@ -108455,6 +109103,10 @@ var init_refresh = __esm({
|
|
|
108455
109103
|
return { ...rest, errored: true, errorReason: disclosure };
|
|
108456
109104
|
});
|
|
108457
109105
|
};
|
|
109106
|
+
FOLD_ERASED_COVERAGE_TYPES = /* @__PURE__ */ new Set([
|
|
109107
|
+
"Report",
|
|
109108
|
+
"Dashboard"
|
|
109109
|
+
]);
|
|
108458
109110
|
buildCoverageEntries2 = (counts, skippedDirectories, requestedTypes, sourceRoot, failures, confirmedTypes) => {
|
|
108459
109111
|
const failuresByType = aggregateFailuresByType(sourceRoot, failures);
|
|
108460
109112
|
const entries = [];
|
|
@@ -108462,7 +109114,8 @@ var init_refresh = __esm({
|
|
|
108462
109114
|
const requested = requestedTypes === null || requestedTypes.has(type);
|
|
108463
109115
|
const failureInfo = failuresByType.get(type);
|
|
108464
109116
|
const errored = failureInfo !== void 0 && failureInfo.count > 0;
|
|
108465
|
-
const
|
|
109117
|
+
const foldErased = FOLD_ERASED_COVERAGE_TYPES.has(type);
|
|
109118
|
+
const retrieveConfirmed = confirmedTypes !== null && confirmedTypes.has(type) && !errored && !foldErased;
|
|
108466
109119
|
entries.push({
|
|
108467
109120
|
type,
|
|
108468
109121
|
requested,
|
|
@@ -108470,6 +109123,7 @@ var init_refresh = __esm({
|
|
|
108470
109123
|
errored,
|
|
108471
109124
|
...errored ? { errorReason: failureInfo.sampleReason } : {},
|
|
108472
109125
|
neverModeled: false,
|
|
109126
|
+
...foldErased ? { pending: true } : {},
|
|
108473
109127
|
...retrieveConfirmed ? { retrieveConfirmed: true } : {}
|
|
108474
109128
|
});
|
|
108475
109129
|
}
|
|
@@ -108493,21 +109147,44 @@ var init_refresh = __esm({
|
|
|
108493
109147
|
decorateReportsCapCoverage = (entries, stats) => {
|
|
108494
109148
|
if (stats === void 0)
|
|
108495
109149
|
return entries;
|
|
108496
|
-
const
|
|
108497
|
-
|
|
108498
|
-
|
|
108499
|
-
|
|
108500
|
-
|
|
108501
|
-
|
|
108502
|
-
|
|
108503
|
-
|
|
108504
|
-
|
|
108505
|
-
|
|
108506
|
-
|
|
109150
|
+
const landed = /* @__PURE__ */ new Map([
|
|
109151
|
+
["Report", stats.reports],
|
|
109152
|
+
["Dashboard", stats.dashboards]
|
|
109153
|
+
]);
|
|
109154
|
+
const proven = (c) => c.retrieved > 0 && c.total <= c.retrieved;
|
|
109155
|
+
const decorated = (c) => ({
|
|
109156
|
+
requested: true,
|
|
109157
|
+
// This is the ONLY place a fold-erased row gets a truthful `retrieved`:
|
|
109158
|
+
// the files the retrieve actually landed on disk. Overwriting the
|
|
109159
|
+
// structurally-zero node count is the point (FOLD_ERASED_COVERAGE_TYPES).
|
|
109160
|
+
retrieved: c.retrieved,
|
|
109161
|
+
...proven(c) ? { retrieveConfirmed: true } : { pending: true }
|
|
109162
|
+
});
|
|
109163
|
+
const out = entries.map((entry) => {
|
|
109164
|
+
const c = landed.get(entry.type);
|
|
109165
|
+
if (c === void 0 || entry.neverModeled)
|
|
109166
|
+
return entry;
|
|
109167
|
+
const { pending: _wasPending, retrieveConfirmed: _wasConfirmed, ...rest } = entry;
|
|
109168
|
+
return { ...rest, ...decorated(c) };
|
|
109169
|
+
});
|
|
109170
|
+
for (const [type, c] of landed) {
|
|
109171
|
+
if (!out.some((entry) => entry.type === type && !entry.neverModeled)) {
|
|
109172
|
+
out.push({ type, errored: false, neverModeled: false, ...decorated(c) });
|
|
108507
109173
|
}
|
|
108508
109174
|
}
|
|
108509
109175
|
return [...out].sort((a, b) => a.type < b.type ? -1 : a.type > b.type ? 1 : 0);
|
|
108510
109176
|
};
|
|
109177
|
+
decorateReportPullCoverage = (entries, disclosure) => {
|
|
109178
|
+
if (disclosure === void 0)
|
|
109179
|
+
return entries;
|
|
109180
|
+
const reason = `${REPORT_PULL_DISCLOSURE} (${disclosure.mode} pull, ${disclosure.outcome} at ${disclosure.attemptedAt}): ${disclosure.error}`;
|
|
109181
|
+
return entries.map((entry) => {
|
|
109182
|
+
if (!FOLD_ERASED_COVERAGE_TYPES.has(entry.type) || entry.neverModeled)
|
|
109183
|
+
return entry;
|
|
109184
|
+
const { retrieveConfirmed: _dropped, ...rest } = entry;
|
|
109185
|
+
return { ...rest, requested: true, pending: true, errored: true, errorReason: reason };
|
|
109186
|
+
});
|
|
109187
|
+
};
|
|
108511
109188
|
countGraphTotals = async (store) => {
|
|
108512
109189
|
try {
|
|
108513
109190
|
const components = {};
|
|
@@ -108649,14 +109326,18 @@ var init_refresh = __esm({
|
|
|
108649
109326
|
return failed(started, `countGraphTotals: ${counted.error}`, walked.failures, EMPTY_COUNTS, walked.skippedDirectories);
|
|
108650
109327
|
}
|
|
108651
109328
|
return {
|
|
108652
|
-
|
|
109329
|
+
// A report-pull failure forces `partial` for the same reason a
|
|
109330
|
+
// profile-grant disclosure does: the vault built, but a coverage axis it
|
|
109331
|
+
// attempted is unproven. `success` here is what let the failure vanish.
|
|
109332
|
+
status: walked.failures.length === 0 && args.retrieveFailures.length === 0 && args.reportPull === void 0 ? "success" : "partial",
|
|
108653
109333
|
counts: counted.value,
|
|
108654
109334
|
skippedDirectories: walked.skippedDirectories,
|
|
108655
109335
|
errors: walked.failures,
|
|
108656
109336
|
durationMs: Date.now() - started,
|
|
108657
109337
|
...args.retrieveFailures.length > 0 ? { retrieveFailures: args.retrieveFailures } : {},
|
|
108658
109338
|
...toolingApiSummary !== void 0 ? { toolingApi: toolingApiSummary } : {},
|
|
108659
|
-
...args.reportsCapStats !== void 0 ? { reportsCap: args.reportsCapStats } : {}
|
|
109339
|
+
...args.reportsCapStats !== void 0 ? { reportsCap: args.reportsCapStats } : {},
|
|
109340
|
+
...args.reportPull !== void 0 ? { reportPull: args.reportPull } : {}
|
|
108660
109341
|
};
|
|
108661
109342
|
}
|
|
108662
109343
|
const midBuild = opts.stagedMarker !== void 0;
|
|
@@ -108696,7 +109377,7 @@ var init_refresh = __esm({
|
|
|
108696
109377
|
profileGrantStats = computeProfileGrantStats(walked.results);
|
|
108697
109378
|
profileGrantDisclosure = assessProfileGrantIntegrity(profileGrantStats, previousManifest?.edges["grantedBy"] ?? null, counts.edges["grantedBy"] ?? 0);
|
|
108698
109379
|
}
|
|
108699
|
-
const coverage = decorateProfileGrantCoverage(decorateReportsCapCoverage(
|
|
109380
|
+
const coverage = decorateProfileGrantCoverage(decoratePendingCoverage(decorateReportPullCoverage(decorateReportsCapCoverage(buildCoverageEntries2(counts, walked.skippedDirectories, requestedTypes, paths.source, walked.failures, confirmedTypes), args.reportsCapStats), args.reportPull), opts.stagedMarker), profileGrantDisclosure);
|
|
108700
109381
|
let phantomSummary;
|
|
108701
109382
|
if (!midBuild && opts.stagedMarker === void 0) {
|
|
108702
109383
|
try {
|
|
@@ -108722,6 +109403,7 @@ var init_refresh = __esm({
|
|
|
108722
109403
|
...opts.stagedMarker !== void 0 ? { staged: opts.stagedMarker } : {},
|
|
108723
109404
|
...args.apexAstStats !== void 0 ? { apexAst: args.apexAstStats } : {},
|
|
108724
109405
|
...args.reportsCapStats !== void 0 ? { reportsCap: args.reportsCapStats } : {},
|
|
109406
|
+
...args.reportPull !== void 0 ? { reportPull: args.reportPull } : {},
|
|
108725
109407
|
...profileGrantDisclosure !== null && profileGrantStats !== null ? {
|
|
108726
109408
|
profileGrantIntegrity: {
|
|
108727
109409
|
degraded: true,
|
|
@@ -108847,7 +109529,11 @@ var init_refresh = __esm({
|
|
|
108847
109529
|
return {
|
|
108848
109530
|
// A profile-grant integrity disclosure forces `partial`: the vault built,
|
|
108849
109531
|
// but its permission graph is untrustworthy — never report clean success.
|
|
108850
|
-
|
|
109532
|
+
// A report-pull failure forces it for the same reason: the pull was
|
|
109533
|
+
// attempted and did not deliver, so Report/Dashboard coverage is unproven.
|
|
109534
|
+
// (`sfi refresh` exits non-zero on non-success, which is the point — the
|
|
109535
|
+
// shipped defect was a clean exit over a pull that had errored.)
|
|
109536
|
+
status: walked.failures.length === 0 && args.retrieveFailures.length === 0 && profileGrantDisclosure === null && args.reportPull === void 0 ? "success" : "partial",
|
|
108851
109537
|
counts,
|
|
108852
109538
|
skippedDirectories: walked.skippedDirectories,
|
|
108853
109539
|
errors: walked.failures,
|
|
@@ -108858,6 +109544,7 @@ var init_refresh = __esm({
|
|
|
108858
109544
|
...pulse !== void 0 ? { pulse } : {},
|
|
108859
109545
|
...toolingApiSummary !== void 0 ? { toolingApi: toolingApiSummary } : {},
|
|
108860
109546
|
...args.reportsCapStats !== void 0 ? { reportsCap: args.reportsCapStats } : {},
|
|
109547
|
+
...args.reportPull !== void 0 ? { reportPull: args.reportPull } : {},
|
|
108861
109548
|
...auditTrailSummary !== void 0 ? { auditTrail: auditTrailSummary } : {}
|
|
108862
109549
|
};
|
|
108863
109550
|
};
|
|
@@ -109554,6 +110241,7 @@ ${card.body}
|
|
|
109554
110241
|
const succeeded = [];
|
|
109555
110242
|
const failures = [];
|
|
109556
110243
|
let deletedCount = 0;
|
|
110244
|
+
const reconcileRefusals = [];
|
|
109557
110245
|
const attempt = async (types) => {
|
|
109558
110246
|
if (types.length === 0)
|
|
109559
110247
|
return;
|
|
@@ -109561,6 +110249,9 @@ ${card.body}
|
|
|
109561
110249
|
if (result.ok) {
|
|
109562
110250
|
succeeded.push(...types);
|
|
109563
110251
|
deletedCount += result.value.deletedCount;
|
|
110252
|
+
if (result.value.reconcileRefused === true) {
|
|
110253
|
+
reconcileRefusals.push(result.value.reconcileRefusalReason ?? `reconcile refused for ${types.join(", ")} (suspected layout mismatch)`);
|
|
110254
|
+
}
|
|
109564
110255
|
return;
|
|
109565
110256
|
}
|
|
109566
110257
|
if (toAtomicUnits(types, atomicGroup).length <= 1 || classifyRetrieveError(result.error) === "global") {
|
|
@@ -109573,7 +110264,7 @@ ${card.body}
|
|
|
109573
110264
|
await attempt(right);
|
|
109574
110265
|
};
|
|
109575
110266
|
await attempt(allTypes);
|
|
109576
|
-
return { succeeded, deletedCount, failures };
|
|
110267
|
+
return { succeeded, deletedCount, failures, reconcileRefusals };
|
|
109577
110268
|
};
|
|
109578
110269
|
retrieveTypeBatch = async (targetOrg, sourceDir, types, orgObjects) => {
|
|
109579
110270
|
const stamp = `${Date.now()}-${types.length}-${types[0] ?? "batch"}`;
|
|
@@ -109588,7 +110279,13 @@ ${card.body}
|
|
|
109588
110279
|
await runSf(["project", "retrieve", "start", "--manifest", manifestPath, "--target-org", targetOrg], { maxBuffer: SF_MAX_BUFFER, cwd: projectDir, timeout: SF_RETRIEVE_TIMEOUT_MS });
|
|
109589
110280
|
const reconcile2 = await reconcileSourceDeletions(sourceDir, pkgDir, new Set(types));
|
|
109590
110281
|
await syncAuthoritativeRetrieveIntoSource(sourceDir, pkgDir);
|
|
109591
|
-
return ok({
|
|
110282
|
+
return ok({
|
|
110283
|
+
deletedCount: reconcile2.deletedCount,
|
|
110284
|
+
...reconcile2.refused === true ? {
|
|
110285
|
+
reconcileRefused: true,
|
|
110286
|
+
...reconcile2.refusalReason !== void 0 ? { reconcileRefusalReason: reconcile2.refusalReason } : {}
|
|
110287
|
+
} : {}
|
|
110288
|
+
});
|
|
109592
110289
|
} catch (cause) {
|
|
109593
110290
|
return err(cause instanceof Error ? cause.message : String(cause));
|
|
109594
110291
|
} finally {
|
|
@@ -109646,6 +110343,7 @@ ${card.body}
|
|
|
109646
110343
|
await writeFile14(join43(projectDir, "sfdx-project.json"), `{"packageDirectories":[{"path":"force-app","default":true}],"sourceApiVersion":"${SF_API_VERSION3}"}
|
|
109647
110344
|
`, "utf8");
|
|
109648
110345
|
await writeFile14(manifestPath, args.manifestXml, "utf8");
|
|
110346
|
+
const pkgDir = join43(projectDir, "force-app");
|
|
109649
110347
|
try {
|
|
109650
110348
|
await runSfFn([
|
|
109651
110349
|
"project",
|
|
@@ -109654,10 +110352,9 @@ ${card.body}
|
|
|
109654
110352
|
"--manifest",
|
|
109655
110353
|
manifestPath,
|
|
109656
110354
|
"--target-org",
|
|
109657
|
-
args.targetOrg
|
|
109658
|
-
"--output-dir",
|
|
109659
|
-
args.outputDir
|
|
110355
|
+
args.targetOrg
|
|
109660
110356
|
], { maxBuffer: SF_MAX_BUFFER, cwd: projectDir, timeout: SF_RETRIEVE_TIMEOUT_MS });
|
|
110357
|
+
await syncAuthoritativeRetrieveIntoSource(args.outputDir, pkgDir);
|
|
109661
110358
|
} finally {
|
|
109662
110359
|
await rm5(manifestPath, { force: true }).catch(() => {
|
|
109663
110360
|
});
|
|
@@ -109728,6 +110425,45 @@ ${typesXml}
|
|
|
109728
110425
|
`;
|
|
109729
110426
|
return { membersByType, reports, dashboards, manifestXml };
|
|
109730
110427
|
};
|
|
110428
|
+
REPORT_RETRIEVE_BATCH_SIZE = 500;
|
|
110429
|
+
folderedReportPackageXml = (type, members) => `<?xml version="1.0" encoding="UTF-8"?>
|
|
110430
|
+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
110431
|
+
<types>
|
|
110432
|
+
${members.map((m) => ` <members>${m}</members>`).join("\n")}
|
|
110433
|
+
<name>${type}</name>
|
|
110434
|
+
</types>
|
|
110435
|
+
<version>${SF_API_VERSION3}</version>
|
|
110436
|
+
</Package>
|
|
110437
|
+
`;
|
|
110438
|
+
chunkFolderedReportManifest = (membersByType, size = REPORT_RETRIEVE_BATCH_SIZE) => {
|
|
110439
|
+
const batchSize = Number.isFinite(size) && size > 0 ? Math.floor(size) : REPORT_RETRIEVE_BATCH_SIZE;
|
|
110440
|
+
const out = [];
|
|
110441
|
+
for (const type of ["Report", "Dashboard"]) {
|
|
110442
|
+
const members = membersByType[type];
|
|
110443
|
+
for (let i = 0; i < members.length; i += batchSize) {
|
|
110444
|
+
const slice = members.slice(i, i + batchSize);
|
|
110445
|
+
out.push({ type, members: slice, manifestXml: folderedReportPackageXml(type, slice) });
|
|
110446
|
+
}
|
|
110447
|
+
}
|
|
110448
|
+
return out;
|
|
110449
|
+
};
|
|
110450
|
+
retrieveFolderedReportBatches = async (args, runSfFn = runSf) => {
|
|
110451
|
+
const batches = chunkFolderedReportManifest(args.membersByType);
|
|
110452
|
+
const batchErrors = [];
|
|
110453
|
+
for (const [index, batch] of batches.entries()) {
|
|
110454
|
+
try {
|
|
110455
|
+
await retrieveAdditiveManifest({
|
|
110456
|
+
targetOrg: args.targetOrg,
|
|
110457
|
+
outputDir: args.outputDir,
|
|
110458
|
+
manifestXml: batch.manifestXml,
|
|
110459
|
+
tempLabel: `${args.tempLabel}-${index + 1}`
|
|
110460
|
+
}, runSfFn);
|
|
110461
|
+
} catch (cause) {
|
|
110462
|
+
batchErrors.push(`${batch.type} batch ${index + 1}/${batches.length} (${batch.members.length} member(s)) failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
110463
|
+
}
|
|
110464
|
+
}
|
|
110465
|
+
return { batches: batches.length, batchErrors };
|
|
110466
|
+
};
|
|
109731
110467
|
REPORT_META_SUFFIXES = {
|
|
109732
110468
|
Report: ".report-meta.xml",
|
|
109733
110469
|
Dashboard: ".dashboard-meta.xml"
|
|
@@ -109794,19 +110530,18 @@ ${typesXml}
|
|
|
109794
110530
|
return [];
|
|
109795
110531
|
}
|
|
109796
110532
|
};
|
|
109797
|
-
const { reports, dashboards, manifestXml } = buildFolderedReportManifest({
|
|
110533
|
+
const { reports, dashboards, manifestXml, membersByType } = buildFolderedReportManifest({
|
|
109798
110534
|
folders,
|
|
109799
110535
|
reports: await queryRecords("Report"),
|
|
109800
110536
|
dashboards: await queryRecords("Dashboard")
|
|
109801
110537
|
});
|
|
109802
110538
|
if (manifestXml === null)
|
|
109803
|
-
return ok({ reports: 0, dashboards: 0 });
|
|
109804
|
-
|
|
109805
|
-
|
|
109806
|
-
return
|
|
109807
|
-
} catch (cause) {
|
|
109808
|
-
return err(`report/dashboard retrieve failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
110539
|
+
return ok({ reports: 0, dashboards: 0, batchErrors: [] });
|
|
110540
|
+
const outcome = await retrieveFolderedReportBatches({ targetOrg, outputDir: sourceDir, tempLabel: "reports", membersByType }, runSfFn);
|
|
110541
|
+
if (outcome.batches > 0 && outcome.batchErrors.length === outcome.batches) {
|
|
110542
|
+
return err(`report/dashboard retrieve failed: all ${outcome.batches} batch(es) errored \u2014 ${outcome.batchErrors[0] ?? ""}`);
|
|
109809
110543
|
}
|
|
110544
|
+
return ok({ reports, dashboards, batchErrors: outcome.batchErrors });
|
|
109810
110545
|
};
|
|
109811
110546
|
reportsCap = () => {
|
|
109812
110547
|
const raw = Number(process.env["SFI_REPORTS_CAP"]);
|
|
@@ -109854,24 +110589,31 @@ ${typesXml}
|
|
|
109854
110589
|
dashboards: await ranked("Dashboard", "LastViewedDate")
|
|
109855
110590
|
});
|
|
109856
110591
|
if (manifestXml === null) {
|
|
109857
|
-
return ok({ reports: 0, dashboards: 0, totals, landed: { reports: 0, dashboards: 0 }, missing: [] });
|
|
109858
|
-
}
|
|
109859
|
-
try {
|
|
109860
|
-
await retrieveAdditiveManifest({ targetOrg, outputDir: sourceDir, manifestXml, tempLabel: "smart-reports" }, runSfFn);
|
|
109861
|
-
const fidelity = countLandedReportMembers(membersByType, await collectReportMetaFiles(sourceDir));
|
|
109862
110592
|
return ok({
|
|
109863
|
-
reports,
|
|
109864
|
-
dashboards,
|
|
110593
|
+
reports: 0,
|
|
110594
|
+
dashboards: 0,
|
|
109865
110595
|
totals,
|
|
109866
|
-
landed: { reports:
|
|
109867
|
-
missing: [
|
|
109868
|
-
|
|
109869
|
-
...fidelity.Dashboard.missing.map((m) => `Dashboard:${m}`)
|
|
109870
|
-
]
|
|
110596
|
+
landed: { reports: 0, dashboards: 0 },
|
|
110597
|
+
missing: [],
|
|
110598
|
+
batchErrors: []
|
|
109871
110599
|
});
|
|
109872
|
-
} catch (cause) {
|
|
109873
|
-
return err(`smart report retrieve failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
109874
110600
|
}
|
|
110601
|
+
const outcome = await retrieveFolderedReportBatches({ targetOrg, outputDir: sourceDir, tempLabel: "smart-reports", membersByType }, runSfFn);
|
|
110602
|
+
if (outcome.batches > 0 && outcome.batchErrors.length === outcome.batches) {
|
|
110603
|
+
return err(`smart report retrieve failed: all ${outcome.batches} batch(es) errored \u2014 ${outcome.batchErrors[0] ?? ""}`);
|
|
110604
|
+
}
|
|
110605
|
+
const fidelity = countLandedReportMembers(membersByType, await collectReportMetaFiles(sourceDir));
|
|
110606
|
+
return ok({
|
|
110607
|
+
reports,
|
|
110608
|
+
dashboards,
|
|
110609
|
+
totals,
|
|
110610
|
+
landed: { reports: fidelity.Report.landed, dashboards: fidelity.Dashboard.landed },
|
|
110611
|
+
missing: [
|
|
110612
|
+
...fidelity.Report.missing.map((m) => `Report:${m}`),
|
|
110613
|
+
...fidelity.Dashboard.missing.map((m) => `Dashboard:${m}`)
|
|
110614
|
+
],
|
|
110615
|
+
batchErrors: outcome.batchErrors
|
|
110616
|
+
});
|
|
109875
110617
|
};
|
|
109876
110618
|
formatFailure = (failure) => ` ${failure.path}: ${failure.error.kind} \u2014 ${failure.error.message}`;
|
|
109877
110619
|
formatBlock = (label, counts) => {
|
|
@@ -109942,6 +110684,9 @@ ${typesXml}
|
|
|
109942
110684
|
if (result.reportsCap !== void 0) {
|
|
109943
110685
|
lines.push("", ...formatReportsCapSummary(result.reportsCap));
|
|
109944
110686
|
}
|
|
110687
|
+
if (result.reportPull !== void 0) {
|
|
110688
|
+
lines.push("", `WARNING \u2014 ${REPORT_PULL_DISCLOSURE}.`, ` ${result.reportPull.mode} pull ${result.reportPull.outcome} at ${result.reportPull.attemptedAt}: ${result.reportPull.error}`, " Report/Dashboard coverage rows are marked errored + pending, and this run is recorded on the manifest as `reportPull`.", ' A "0 reports" answer from this vault means NOT CHECKED, not none. Re-run `sfi refresh` (or `--with-reports`) to prove coverage.');
|
|
110689
|
+
}
|
|
109945
110690
|
const skippedWarning = formatSkippedWarning(result.skippedDirectories);
|
|
109946
110691
|
if (skippedWarning !== null) {
|
|
109947
110692
|
lines.push("", ...skippedWarning);
|
|
@@ -110155,7 +110900,7 @@ ${typesXml}
|
|
|
110155
110900
|
`;
|
|
110156
110901
|
};
|
|
110157
110902
|
registerRefreshCommand = (program) => {
|
|
110158
|
-
program.command("refresh").description("Refresh the vault from a live Salesforce org").option("--target-org <alias>", "Salesforce org alias to retrieve from (overrides config.json)").option("--no-pull", "Skip `sf project retrieve` and use the existing source tree").option("--types <list>", "Comma-separated metadata types to restrict the refresh to").option("--with-data-shape", "After the refresh, capture a small budgeted set of record-DATA observations (approximate per-object record counts + recent-sample field fill rates) into the graph facts table. OPT-IN twice over: requires live consent for the org (sfi.live_consent / SFI_LIVE_PLANE_ENABLED) \u2014 without it the capture skips honestly and the refresh stays offline. Read-only; at most SFI_DATA_SHAPE_BUDGET (default 60) API calls.").option("--with-tooling-api", "After the offline refresh completes, run the v1.7 Tooling API enrichment pass to hydrate `lastModifiedDate` / `lastModifiedBy` / `apiVersion` on enrichable nodes, and now also confirms declared dependencies (stamps `confirmedByApi` on matching edges and appends `dependsOnFromApi` edges from MetadataComponentDependency). Requires `sf` CLI installed and the target org alias authenticated.").option("--with-audit-trail", "During refresh, query SetupAuditTrail and append new rows (deduped by Id) to meta/setup-audit-trail.jsonl so sfi.component_change_attribution can answer who-changed-this offline. Opt-in \u2014 touches the org and adds latency. First run pulls LAST_N_DAYS:180; later runs are incremental. Additive-only: persisted history survives after Salesforce drops rows from the live 180-day window.").option("--incremental", "Reuse the previous refresh's per-file extraction cache (`meta/extract-cache.json`): source files whose mtime + size are unchanged skip re-extraction. The graph is still rebuilt in full, so results are identical to a cold refresh \u2014 only faster. The cache is invalidated automatically on a version upgrade.").option("--incremental-graph", "Re-import ONLY the changed nodes/edges into the graph (a transactional change-set diff against the current graph) instead of rebuilding it in full, when a prior non-empty graph exists. Provably byte-identical to a cold rebuild; falls back to a full rebuild on an empty/largely-changed graph or any apply error. Independent of `--incremental` (the parse cache); combine both for the largest win. Off by default.").option("--components <list>", "Demand-retrieve: comma-separated component ids (e.g. `CustomObject:Foo__c`) to pull on demand. Only AUTOMATION-CRITICAL phantoms are retrieved; grant-only / managed / standard / blindspot ids are refused with the reason. Generalizes the automatic B29 expansion to a user-triggered pull.").option("--no-reports", "Skip the report/dashboard pull entirely. DEFAULT (neither flag): pull the top SFI_REPORTS_CAP (500) reports+dashboards ranked by actual usage (LastRunDate / LastViewedDate, fallback LastModifiedDate) and fold their field references onto fields; when the org holds more than the cap, Report/Dashboard coverage reads `pending` so absence claims stay qualified.").option("--with-reports", "Also pull folder-based Report / Dashboard metadata and fold their field usage onto the referenced fields, so a field used only in a report column / dashboard component stops reading as unused. Off by default \u2014 slow on large orgs (enumerates folders + pulls every report/dashboard).").option("--staged", "Build the vault in tiers for fast time-to-first-insight: T0 skeleton (read-only COUNT queries, no retrieve) \u2192 T1 priority types (objects/fields/automation/Apex/permissions; Markdown render deferred) \u2192 T2 full refresh (transactional side-build \u2014 a mid-tier failure leaves the prior state servable) \u2192 optional T3 with --with-reports. Mid-build, health_check reports degraded (building tier i/n) and queued types read as pending coverage, so absence claims stay qualified. Resumable: a re-run skips completed tiers. The final tier is a plain monolithic refresh, so the end state is identical to a non-staged run.").option("--no-apex-ast", "Skip the parser-grade Apex pass (ON by default): every class/trigger is re-parsed with the vendored ANTLR grammar adding `confidence: 'parsed'`, `source: 'apex-ast'` readsFrom/writesTo/callsApex edges \u2014 including SOQL field-level reads and constant-string Database.query literals. Exact-duplicate heuristic scanner edges are dropped (no double-counting); scanner-only edges are kept. A file that fails to parse falls back to scanner-only (counted in the manifest's apexAst block).").option("--drain-demand-queue", "Drain the phantom demand queue (`meta/demand-queue.jsonl`): retrieve every QUEUED automation-critical component that MCP consumers hit as phantoms (recorded by sfi.get_component), through the same gate as --components \u2014 grant-only / managed / standard / blindspot ids are refused with the reason. Dedup and idempotency are structural: N hits on one id drain once, and a re-drain of an already-drained id is a no-op. Drained entries are marked in the queue with their outcome.").action(async (flags) => {
|
|
110903
|
+
program.command("refresh").description("Refresh the vault from a live Salesforce org").option("--target-org <alias>", "Salesforce org alias to retrieve from (overrides config.json)").option("--no-pull", "Skip `sf project retrieve` and use the existing source tree").option("--types <list>", "Comma-separated metadata types to restrict the refresh to").option("--with-data-shape", "After the refresh, capture a small budgeted set of record-DATA observations (approximate per-object record counts + recent-sample field fill rates) into the graph facts table. OPT-IN twice over: requires live consent for the org (sfi.live_consent / SFI_LIVE_PLANE_ENABLED) \u2014 without it the capture skips honestly and the refresh stays offline. Read-only; at most SFI_DATA_SHAPE_BUDGET (default 60) API calls.").option("--with-tooling-api", "After the offline refresh completes, run the v1.7 Tooling API enrichment pass to hydrate `lastModifiedDate` / `lastModifiedBy` / `apiVersion` on enrichable nodes, and now also confirms declared dependencies (stamps `confirmedByApi` on matching edges and appends `dependsOnFromApi` edges from MetadataComponentDependency). Requires `sf` CLI installed and the target org alias authenticated.").option("--with-audit-trail", "During refresh, query SetupAuditTrail and append new rows (deduped by Id) to meta/setup-audit-trail.jsonl so sfi.component_change_attribution can answer who-changed-this offline. Opt-in \u2014 touches the org and adds latency. First run pulls LAST_N_DAYS:180; later runs are incremental. Additive-only: persisted history survives after Salesforce drops rows from the live 180-day window.").option("--incremental", "Reuse the previous refresh's per-file extraction cache (`meta/extract-cache.json`): source files whose mtime + size are unchanged skip re-extraction. The graph is still rebuilt in full, so results are identical to a cold refresh \u2014 only faster. The cache is invalidated automatically on a version upgrade.").option("--incremental-graph", "Re-import ONLY the changed nodes/edges into the graph (a transactional change-set diff against the current graph) instead of rebuilding it in full, when a prior non-empty graph exists. Provably byte-identical to a cold rebuild; falls back to a full rebuild on an empty/largely-changed graph or any apply error. Independent of `--incremental` (the parse cache); combine both for the largest win. Off by default.").option("--components <list>", "Demand-retrieve: comma-separated component ids (e.g. `CustomObject:Foo__c`) to pull on demand. Only AUTOMATION-CRITICAL phantoms are retrieved; grant-only / managed / standard / blindspot ids are refused with the reason. Generalizes the automatic B29 expansion to a user-triggered pull.").option("--no-reports", "Skip the report/dashboard pull entirely. DEFAULT (neither flag): pull the top SFI_REPORTS_CAP (500) reports+dashboards ranked by actual usage (LastRunDate / LastViewedDate, fallback LastModifiedDate) and fold their field references onto fields; when the org holds more than the cap, Report/Dashboard coverage reads `pending` so absence claims stay qualified. Report/Dashboard nodes are folded onto fields and never persisted, so their coverage row can only ever be proven by the pull itself \u2014 it reads `pending` (not checked), never a confirmed zero. A pull that errors is non-fatal but NOT silent: it is recorded on the manifest as `reportPull`, marks those rows errored, and exits non-zero.").option("--with-reports", "Also pull folder-based Report / Dashboard metadata and fold their field usage onto the referenced fields, so a field used only in a report column / dashboard component stops reading as unused. Off by default \u2014 slow on large orgs (enumerates folders + pulls every report/dashboard).").option("--staged", "Build the vault in tiers for fast time-to-first-insight: T0 skeleton (read-only COUNT queries, no retrieve) \u2192 T1 priority types (objects/fields/automation/Apex/permissions; Markdown render deferred) \u2192 T2 full refresh (transactional side-build \u2014 a mid-tier failure leaves the prior state servable) \u2192 optional T3 with --with-reports. Mid-build, health_check reports degraded (building tier i/n) and queued types read as pending coverage, so absence claims stay qualified. Resumable: a re-run skips completed tiers. The final tier is a plain monolithic refresh, so the end state is identical to a non-staged run.").option("--no-apex-ast", "Skip the parser-grade Apex pass (ON by default): every class/trigger is re-parsed with the vendored ANTLR grammar adding `confidence: 'parsed'`, `source: 'apex-ast'` readsFrom/writesTo/callsApex edges \u2014 including SOQL field-level reads and constant-string Database.query literals. Exact-duplicate heuristic scanner edges are dropped (no double-counting); scanner-only edges are kept. A file that fails to parse falls back to scanner-only (counted in the manifest's apexAst block).").option("--drain-demand-queue", "Drain the phantom demand queue (`meta/demand-queue.jsonl`): retrieve every QUEUED automation-critical component that MCP consumers hit as phantoms (recorded by sfi.get_component), through the same gate as --components \u2014 grant-only / managed / standard / blindspot ids are refused with the reason. Dedup and idempotency are structural: N hits on one id drain once, and a re-drain of an already-drained id is a no-op. Drained entries are marked in the queue with their outcome.").action(async (flags) => {
|
|
110159
110904
|
if (flags.drainDemandQueue === true) {
|
|
110160
110905
|
const configResult = await loadVaultConfig(process.cwd());
|
|
110161
110906
|
if (!configResult.ok) {
|
|
@@ -111357,7 +112102,7 @@ var makeShutdownOnce = (ctx) => {
|
|
|
111357
112102
|
|
|
111358
112103
|
// dist/src/commands/demo.js
|
|
111359
112104
|
init_refresh();
|
|
111360
|
-
var buildVersion = () => true ? "0.2.
|
|
112105
|
+
var buildVersion = () => true ? "0.2.5" : "dev";
|
|
111361
112106
|
var SHUTDOWN_SIGNALS2 = ["SIGINT", "SIGTERM"];
|
|
111362
112107
|
var resolveDemoSource = () => {
|
|
111363
112108
|
let dir = dirname25(fileURLToPath5(import.meta.url));
|
|
@@ -112812,7 +113557,7 @@ init_vault_git();
|
|
|
112812
113557
|
init_watch();
|
|
112813
113558
|
var readVersion = () => {
|
|
112814
113559
|
if (true)
|
|
112815
|
-
return "0.2.
|
|
113560
|
+
return "0.2.5";
|
|
112816
113561
|
const pkgUrl = new URL("../../package.json", import.meta.url);
|
|
112817
113562
|
const raw = readFileSync10(fileURLToPath6(pkgUrl), "utf8");
|
|
112818
113563
|
const parsed = JSON.parse(raw);
|