sf-intelligence 0.1.22 → 0.1.24
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/data/embedding-index.json +67943 -0
- package/dist/index.js +2340 -151
- package/package.json +3 -2
- package/server.json +2 -2
package/dist/index.js
CHANGED
|
@@ -21564,13 +21564,13 @@ var init_auth = __esm({
|
|
|
21564
21564
|
stdout = result.stdout;
|
|
21565
21565
|
} catch (cause) {
|
|
21566
21566
|
const codeBag = cause;
|
|
21567
|
-
|
|
21567
|
+
const stderrOrMsg = codeBag.stderr ?? codeBag.message ?? String(cause);
|
|
21568
|
+
if (codeBag.code === "ENOENT" || stderrOrMsg.includes("is not recognized as an internal or external command")) {
|
|
21568
21569
|
return err({
|
|
21569
21570
|
kind: "sf-cli-missing",
|
|
21570
21571
|
message: "The `sf` CLI is not installed or not on PATH. Install it from https://developer.salesforce.com/tools/sfdxcli and run `sf org login web --target-org <alias>` to authenticate."
|
|
21571
21572
|
});
|
|
21572
21573
|
}
|
|
21573
|
-
const stderrOrMsg = codeBag.stderr ?? codeBag.message ?? String(cause);
|
|
21574
21574
|
if (stderrOrMsg.includes("No authentication for org") || stderrOrMsg.includes("NoOrgFound") || stderrOrMsg.includes("Unknown org")) {
|
|
21575
21575
|
return err({
|
|
21576
21576
|
kind: "org-not-found",
|
|
@@ -22484,9 +22484,13 @@ var init_picklist_literal_check = __esm({
|
|
|
22484
22484
|
// ../mcp/dist/src/tools/live-plane.js
|
|
22485
22485
|
var live_plane_exports = {};
|
|
22486
22486
|
__export(live_plane_exports, {
|
|
22487
|
+
GROUP_MEMBERS_DISCLOSURE: () => GROUP_MEMBERS_DISCLOSURE,
|
|
22487
22488
|
LICENSE_USAGE_DISCLOSURE: () => LICENSE_USAGE_DISCLOSURE,
|
|
22488
22489
|
LIVE_PLANE_DISCLOSURE: () => LIVE_PLANE_DISCLOSURE,
|
|
22490
|
+
PERMSET_HOLDERS_DISCLOSURE: () => PERMSET_HOLDERS_DISCLOSURE,
|
|
22489
22491
|
STALE_CHECK_TYPES: () => STALE_CHECK_TYPES,
|
|
22492
|
+
USER_PERMSETS_DISCLOSURE: () => USER_PERMSETS_DISCLOSURE,
|
|
22493
|
+
ZOMBIE_ACCOUNTS_DISCLOSURE: () => ZOMBIE_ACCOUNTS_DISCLOSURE,
|
|
22490
22494
|
apiPath: () => apiPath,
|
|
22491
22495
|
assertSoqlIdentifier: () => assertSoqlIdentifier,
|
|
22492
22496
|
checkVaultStaleness: () => checkVaultStaleness,
|
|
@@ -22513,6 +22517,8 @@ __export(live_plane_exports, {
|
|
|
22513
22517
|
liveFolderAccessInputSchema: () => liveFolderAccessInputSchema,
|
|
22514
22518
|
liveGroupCountHandler: () => liveGroupCountHandler,
|
|
22515
22519
|
liveGroupCountInputSchema: () => liveGroupCountInputSchema,
|
|
22520
|
+
liveGroupMembersHandler: () => liveGroupMembersHandler,
|
|
22521
|
+
liveGroupMembersInputSchema: () => liveGroupMembersInputSchema,
|
|
22516
22522
|
liveInactiveUsersHandler: () => liveInactiveUsersHandler,
|
|
22517
22523
|
liveInactiveUsersInputSchema: () => liveInactiveUsersInputSchema,
|
|
22518
22524
|
liveLicenseUsageHandler: () => liveLicenseUsageHandler,
|
|
@@ -22523,6 +22529,8 @@ __export(live_plane_exports, {
|
|
|
22523
22529
|
liveOrgLimitsInputSchema: () => liveOrgLimitsInputSchema,
|
|
22524
22530
|
liveOwnerBreakdownHandler: () => liveOwnerBreakdownHandler,
|
|
22525
22531
|
liveOwnerBreakdownInputSchema: () => liveOwnerBreakdownInputSchema,
|
|
22532
|
+
livePermsetHoldersHandler: () => livePermsetHoldersHandler,
|
|
22533
|
+
livePermsetHoldersInputSchema: () => livePermsetHoldersInputSchema,
|
|
22526
22534
|
liveRecentActivityHandler: () => liveRecentActivityHandler,
|
|
22527
22535
|
liveRecentActivityInputSchema: () => liveRecentActivityInputSchema,
|
|
22528
22536
|
liveReportUsageHandler: () => liveReportUsageHandler,
|
|
@@ -22539,13 +22547,17 @@ __export(live_plane_exports, {
|
|
|
22539
22547
|
liveStaleRecordsInputSchema: () => liveStaleRecordsInputSchema,
|
|
22540
22548
|
liveStorageByObjectHandler: () => liveStorageByObjectHandler,
|
|
22541
22549
|
liveStorageByObjectInputSchema: () => liveStorageByObjectInputSchema,
|
|
22550
|
+
liveUserPermsetsHandler: () => liveUserPermsetsHandler,
|
|
22551
|
+
liveUserPermsetsInputSchema: () => liveUserPermsetsInputSchema,
|
|
22552
|
+
liveZombieAccountsHandler: () => liveZombieAccountsHandler,
|
|
22553
|
+
liveZombieAccountsInputSchema: () => liveZombieAccountsInputSchema,
|
|
22542
22554
|
redactSecrets: () => redactSecrets,
|
|
22543
22555
|
resolveLiveAccess: () => resolveLiveAccess,
|
|
22544
22556
|
restGet: () => restGet,
|
|
22545
22557
|
runSfJson: () => runSfJson
|
|
22546
22558
|
});
|
|
22547
22559
|
import { z as z2 } from "zod";
|
|
22548
|
-
var MAX_SAMPLE_ROWS, SAMPLE_BYTE_BUDGET, LIVE_TABLE_ROW_CAP, liveEnabledSchema, isLivePlaneEnabled, liveTrust, resolveOrg, resolveLiveAccess, liveConsentRequiredError, gateLive, liveDescribeInputSchema, liveDescribeHandler, liveCountInputSchema, OBJECT_API_NAME_RE, resolveCountSoql, assertCountSoql, fromObjectOf, NO_VALIDATION, collectPicklistMismatches, scanSoqlForPicklistMismatchesSync, liveCountHandler, liveStaleCheckInputSchema, STALE_CHECK_TYPES, ISO_TIMESTAMP_RE, LIVE_STALE_BOUNDARIES, checkVaultStaleness, liveStaleCheckHandler, liveSampleInputSchema, capSampleSoql, liveSampleHandler, liveFieldPopulationInputSchema, liveFieldPopulationHandler, liveOrgLimitsInputSchema, liveOrgLimitsHandler, MAX_INACTIVE_USER_ROWS, DEFAULT_INACTIVE_USER_ROWS, INACTIVE_USERS_BYTE_BUDGET, DEFAULT_INACTIVE_DAYS, MS_PER_DAY, liveInactiveUsersInputSchema, soqlDateTime, fitInactiveUsers, liveInactiveUsersHandler, DEFAULT_LICENSE_INACTIVE_DAYS, MAX_RECLAIM_ROWS, LICENSE_USAGE_DISCLOSURE, liveLicenseUsageInputSchema, toUtilization, renderLicenseUsageMarkdown, liveLicenseUsageHandler, liveConsentInputSchema, consentTrust, liveConsentHandler, liveQuery, MAX_DETAIL_ROWS, daysAgoSoql, daysSince, livePlaneVaultState, UNAVAILABLE_ERROR, isBudgetExhaustedReason, BUDGET_SIGNAL, assertSoqlIdentifier, soqlLiteral, MAX_GROUP_BUCKETS, DEFAULT_STALE_DAYS, DEFAULT_RECENT_DAYS, liveGroupCountInputSchema, liveGroupCountHandler, liveStaleRecordsInputSchema, liveStaleRecordsHandler, liveRecentActivityInputSchema, liveRecentActivityHandler, buildEqualityWhere, aggregateCountFromRow, liveAggregateInputSchema, liveAggregateHandler, MAX_DUPLICATE_GROUPS, liveDuplicateCheckInputSchema, liveDuplicateCheckHandler, MAX_OWNER_BUCKETS, liveOwnerBreakdownInputSchema, liveOwnerBreakdownHandler, DEFAULT_REPORT_STALE_DAYS, liveReportUsageInputSchema, liveReportUsageHandler, liveFolderAccessInputSchema, liveFolderAccessHandler, DEFAULT_TEMPLATE_STALE_DAYS, CLASSIC_TEMPLATE_TYPES, liveEmailTemplateUsageInputSchema, liveEmailTemplateUsageHandler, DEFAULT_HEALTH_DAYS, LIMIT_RISK_THRESHOLD, liveOrgHealthInputSchema, liveOrgHealthHandler, liveStorageByObjectInputSchema, liveStorageByObjectHandler, DEFAULT_SKEW_THRESHOLD, liveDataSkewInputSchema, liveDataSkewHandler, DEFAULT_AUDIT_DAYS, liveSetupAuditTrailInputSchema, liveSetupAuditTrailHandler, liveSecurityExposureInputSchema, liveSecurityExposureHandler;
|
|
22560
|
+
var MAX_SAMPLE_ROWS, SAMPLE_BYTE_BUDGET, LIVE_TABLE_ROW_CAP, liveEnabledSchema, isLivePlaneEnabled, liveTrust, resolveOrg, resolveLiveAccess, liveConsentRequiredError, gateLive, liveDescribeInputSchema, liveDescribeHandler, liveCountInputSchema, OBJECT_API_NAME_RE, resolveCountSoql, assertCountSoql, fromObjectOf, NO_VALIDATION, collectPicklistMismatches, scanSoqlForPicklistMismatchesSync, liveCountHandler, liveStaleCheckInputSchema, STALE_CHECK_TYPES, ISO_TIMESTAMP_RE, LIVE_STALE_BOUNDARIES, checkVaultStaleness, liveStaleCheckHandler, liveSampleInputSchema, capSampleSoql, liveSampleHandler, liveFieldPopulationInputSchema, liveFieldPopulationHandler, liveOrgLimitsInputSchema, liveOrgLimitsHandler, MAX_INACTIVE_USER_ROWS, DEFAULT_INACTIVE_USER_ROWS, INACTIVE_USERS_BYTE_BUDGET, DEFAULT_INACTIVE_DAYS, MS_PER_DAY, liveInactiveUsersInputSchema, soqlDateTime, fitInactiveUsers, liveInactiveUsersHandler, DEFAULT_LICENSE_INACTIVE_DAYS, MAX_RECLAIM_ROWS, LICENSE_USAGE_DISCLOSURE, liveLicenseUsageInputSchema, toUtilization, renderLicenseUsageMarkdown, liveLicenseUsageHandler, liveConsentInputSchema, consentTrust, liveConsentHandler, liveQuery, MAX_DETAIL_ROWS, daysAgoSoql, daysSince, livePlaneVaultState, UNAVAILABLE_ERROR, isBudgetExhaustedReason, BUDGET_SIGNAL, assertSoqlIdentifier, soqlLiteral, MAX_GROUP_BUCKETS, DEFAULT_STALE_DAYS, DEFAULT_RECENT_DAYS, liveGroupCountInputSchema, liveGroupCountHandler, liveStaleRecordsInputSchema, liveStaleRecordsHandler, liveRecentActivityInputSchema, liveRecentActivityHandler, buildEqualityWhere, aggregateCountFromRow, liveAggregateInputSchema, liveAggregateHandler, MAX_DUPLICATE_GROUPS, liveDuplicateCheckInputSchema, liveDuplicateCheckHandler, MAX_OWNER_BUCKETS, liveOwnerBreakdownInputSchema, liveOwnerBreakdownHandler, DEFAULT_REPORT_STALE_DAYS, liveReportUsageInputSchema, liveReportUsageHandler, liveFolderAccessInputSchema, liveFolderAccessHandler, DEFAULT_TEMPLATE_STALE_DAYS, CLASSIC_TEMPLATE_TYPES, liveEmailTemplateUsageInputSchema, liveEmailTemplateUsageHandler, DEFAULT_HEALTH_DAYS, LIMIT_RISK_THRESHOLD, liveOrgHealthInputSchema, liveOrgHealthHandler, liveStorageByObjectInputSchema, liveStorageByObjectHandler, DEFAULT_SKEW_THRESHOLD, liveDataSkewInputSchema, liveDataSkewHandler, DEFAULT_AUDIT_DAYS, liveSetupAuditTrailInputSchema, liveSetupAuditTrailHandler, liveSecurityExposureInputSchema, liveSecurityExposureHandler, MAX_HOLDER_ROWS, DEFAULT_HOLDER_ROWS, HOLDERS_BYTE_BUDGET, MAX_HOLDER_BUCKETS, PERMSET_HOLDERS_DISCLOSURE, livePermsetHoldersInputSchema, holderNotFoundError, holderAmbiguousError, resolveHolderTarget, toPermsetHolder, renderPermsetHoldersMarkdown, aggregateNumber, livePermsetHoldersHandler, splitHolders, fitPermsetHolders, MAX_ZOMBIE_ROWS, DEFAULT_ZOMBIE_ROWS, ZOMBIE_BYTE_BUDGET, ZOMBIE_CLIENT_DIFF_CAP, ZOMBIE_ACCOUNTS_DISCLOSURE, liveZombieAccountsInputSchema, toZombieUser, renderZombieAccountsMarkdown, fitZombieUsers, liveZombieAccountsHandler, MAX_MEMBER_ROWS, DEFAULT_MEMBER_ROWS, MEMBERS_BYTE_BUDGET, MEMBER_ID_CHUNK, GROUP_MEMBERS_DISCLOSURE, liveGroupMembersInputSchema, idChunks, resolveMemberUsers, renderGroupMembersMarkdown, fitGroupMembers, liveGroupMembersHandler, MAX_USER_PERMSET_ROWS, DEFAULT_USER_PERMSET_ROWS, USER_PERMSETS_BYTE_BUDGET, USER_PERMSETS_DISCLOSURE, liveUserPermsetsInputSchema, renderUserPermsetsMarkdown, fitUserPermsets, liveUserPermsetsHandler;
|
|
22549
22561
|
var init_live_plane = __esm({
|
|
22550
22562
|
"../mcp/dist/src/tools/live-plane.js"() {
|
|
22551
22563
|
"use strict";
|
|
@@ -24184,6 +24196,855 @@ ${renderTrustFooter(trust)}`;
|
|
|
24184
24196
|
vaultState: livePlaneVaultState(ctx)
|
|
24185
24197
|
});
|
|
24186
24198
|
};
|
|
24199
|
+
MAX_HOLDER_ROWS = 500;
|
|
24200
|
+
DEFAULT_HOLDER_ROWS = 100;
|
|
24201
|
+
HOLDERS_BYTE_BUDGET = 36e3;
|
|
24202
|
+
MAX_HOLDER_BUCKETS = 50;
|
|
24203
|
+
PERMSET_HOLDERS_DISCLOSURE = "Read-only; point-in-time as of queriedAt \u2014 never cached beyond the live-session TTL; roster is CURRENT org state, unlike vault answers.";
|
|
24204
|
+
livePermsetHoldersInputSchema = liveEnabledSchema.extend({
|
|
24205
|
+
/** PermissionSet.Name / PSG DeveloperName / Profile.Name (labels accepted). */
|
|
24206
|
+
name: z2.string().min(1),
|
|
24207
|
+
/** What `name` names. Default 'auto': probe PermissionSet → PermissionSetGroup
|
|
24208
|
+
* → Profile by exact name/label; no-match or ambiguity is an honest error,
|
|
24209
|
+
* never a guess. */
|
|
24210
|
+
kind: z2.enum(["permissionSet", "permissionSetGroup", "profile", "auto"]).optional(),
|
|
24211
|
+
/** Include inactive assignees (default false — active users only). */
|
|
24212
|
+
includeInactiveAssignees: z2.boolean().optional(),
|
|
24213
|
+
/** For kind 'permissionSet': also count holders who receive the set through a
|
|
24214
|
+
* PermissionSetGroup containing it (default TRUE — see the PSG trap above). */
|
|
24215
|
+
includeViaGroups: z2.boolean().optional(),
|
|
24216
|
+
/** Aggregation buckets for the headline (default 'profile'). */
|
|
24217
|
+
groupBy: z2.enum(["none", "profile"]).optional(),
|
|
24218
|
+
/** Max detail rows (default 100, hard cap 500); a ~36 KB byte budget may trim
|
|
24219
|
+
* the page further. `totalAssignees` is always the TRUE deduped count. */
|
|
24220
|
+
limit: z2.number().int().min(1).max(MAX_HOLDER_ROWS).optional(),
|
|
24221
|
+
/** Keyset paging: return rows with Id > afterId (use `nextAfterId`). */
|
|
24222
|
+
afterId: z2.string().optional(),
|
|
24223
|
+
orgAlias: z2.string().min(1).optional()
|
|
24224
|
+
});
|
|
24225
|
+
holderNotFoundError = (name, probed) => ({
|
|
24226
|
+
kind: "component-not-found",
|
|
24227
|
+
message: `No exact match for '${name}' \u2014 probed ${probed.join(", ")} by exact name/label in the live org. Check the API name (sfi.resolve can fix typos against the vault), or pass \`kind\` explicitly.`
|
|
24228
|
+
});
|
|
24229
|
+
holderAmbiguousError = (name, kind, count) => ({
|
|
24230
|
+
kind: "invalid-query",
|
|
24231
|
+
message: `'${name}' matches ${count}+ ${kind} records in the live org \u2014 refusing to guess. Use the exact API name (Name/DeveloperName), not a label shared by several.`
|
|
24232
|
+
});
|
|
24233
|
+
resolveHolderTarget = async (org, name, kind, exec) => {
|
|
24234
|
+
const lit = soqlLiteral(name);
|
|
24235
|
+
const probes = [
|
|
24236
|
+
{
|
|
24237
|
+
kind: "permissionSet",
|
|
24238
|
+
// IsOwnedByProfile = false: every Profile owns a system permission set
|
|
24239
|
+
// with a matching name — matching one here would silently answer the
|
|
24240
|
+
// PROFILE question under the permissionSet kind.
|
|
24241
|
+
soql: `SELECT Id, Name FROM PermissionSet WHERE (Name = ${lit} OR Label = ${lit}) AND IsOwnedByProfile = false LIMIT 2`,
|
|
24242
|
+
nameField: "Name"
|
|
24243
|
+
},
|
|
24244
|
+
{
|
|
24245
|
+
kind: "permissionSetGroup",
|
|
24246
|
+
soql: `SELECT Id, DeveloperName FROM PermissionSetGroup WHERE (DeveloperName = ${lit} OR MasterLabel = ${lit}) LIMIT 2`,
|
|
24247
|
+
nameField: "DeveloperName"
|
|
24248
|
+
},
|
|
24249
|
+
{
|
|
24250
|
+
kind: "profile",
|
|
24251
|
+
soql: `SELECT Id, Name FROM Profile WHERE Name = ${lit} LIMIT 2`,
|
|
24252
|
+
nameField: "Name"
|
|
24253
|
+
}
|
|
24254
|
+
];
|
|
24255
|
+
const wanted = kind === void 0 || kind === "auto" ? probes : probes.filter((p2) => p2.kind === kind);
|
|
24256
|
+
const probedKinds = [];
|
|
24257
|
+
for (const probe of wanted) {
|
|
24258
|
+
probedKinds.push(probe.kind);
|
|
24259
|
+
const q2 = await liveQuery(org, probe.soql, exec);
|
|
24260
|
+
if (!q2.available) {
|
|
24261
|
+
return err({
|
|
24262
|
+
kind: "invalid-query",
|
|
24263
|
+
message: `Could not probe ${probe.kind} for '${name}': ${redactSecrets(q2.reason ?? "query failed").slice(0, 160)}`
|
|
24264
|
+
});
|
|
24265
|
+
}
|
|
24266
|
+
if (q2.records.length > 1)
|
|
24267
|
+
return err(holderAmbiguousError(name, probe.kind, q2.records.length));
|
|
24268
|
+
if (q2.records.length === 1) {
|
|
24269
|
+
const row = q2.records[0];
|
|
24270
|
+
return ok({
|
|
24271
|
+
kind: probe.kind,
|
|
24272
|
+
id: String(row.Id ?? ""),
|
|
24273
|
+
name: String(row[probe.nameField] ?? name)
|
|
24274
|
+
});
|
|
24275
|
+
}
|
|
24276
|
+
}
|
|
24277
|
+
return err(holderNotFoundError(name, probedKinds));
|
|
24278
|
+
};
|
|
24279
|
+
toPermsetHolder = (r2) => ({
|
|
24280
|
+
assignmentId: String(r2.Id ?? ""),
|
|
24281
|
+
userId: String(r2.AssigneeId ?? ""),
|
|
24282
|
+
name: String(r2.Assignee?.Name ?? ""),
|
|
24283
|
+
username: String(r2.Assignee?.Username ?? ""),
|
|
24284
|
+
isActive: r2.Assignee?.IsActive === true,
|
|
24285
|
+
profileName: r2.Assignee?.Profile?.Name ?? null,
|
|
24286
|
+
expirationDate: r2.ExpirationDate ?? null,
|
|
24287
|
+
viaGroup: r2.PermissionSetGroupId ? r2.PermissionSetGroup?.DeveloperName ?? r2.PermissionSetGroupId : null
|
|
24288
|
+
});
|
|
24289
|
+
renderPermsetHoldersMarkdown = (data) => {
|
|
24290
|
+
const kindLabel = {
|
|
24291
|
+
permissionSet: "permission set",
|
|
24292
|
+
permissionSetGroup: "permission set group",
|
|
24293
|
+
profile: "profile"
|
|
24294
|
+
};
|
|
24295
|
+
const direct = data.directHolders.length;
|
|
24296
|
+
const viaGroups = (data.viaGroupHolders ?? []).reduce((n2, g2) => n2 + g2.holders.length, 0);
|
|
24297
|
+
const lines = [
|
|
24298
|
+
`### Holders of ${kindLabel[data.target.kind]} \`${data.target.name}\``,
|
|
24299
|
+
"",
|
|
24300
|
+
`**${data.totalAssignees.toLocaleString("en-US")}** ${data.target.kind === "profile" ? "users hold this profile" : "distinct users hold this"}` + (data.target.kind === "permissionSet" ? ` (${direct} direct row(s), ${viaGroups} via-group row(s) on this page)` : "") + (data.expiredExcluded > 0 ? `; ${data.expiredExcluded} expired assignment(s) excluded` : "") + "."
|
|
24301
|
+
];
|
|
24302
|
+
if (data.buckets !== void 0 && data.buckets.length > 0) {
|
|
24303
|
+
lines.push("", "**By profile**", "", mdTable(["Profile", "Holders"], data.buckets.map((b2) => [b2.profileName ?? b2.profileId ?? "(none)", b2.holders])));
|
|
24304
|
+
}
|
|
24305
|
+
const holderRows = (holders) => mdTable(["User", "Username", "Profile", "Active", "Expires"], holders.map((h2) => [
|
|
24306
|
+
h2.name,
|
|
24307
|
+
h2.username,
|
|
24308
|
+
h2.profileName ?? "\u2014",
|
|
24309
|
+
h2.isActive ? "yes" : "no",
|
|
24310
|
+
h2.expirationDate ?? "\u2014"
|
|
24311
|
+
]));
|
|
24312
|
+
if (data.directHolders.length > 0) {
|
|
24313
|
+
lines.push("", data.target.kind === "permissionSet" ? "**Direct holders**" : "**Holders**", "", holderRows(data.directHolders));
|
|
24314
|
+
}
|
|
24315
|
+
for (const g2 of data.viaGroupHolders ?? []) {
|
|
24316
|
+
if (g2.holders.length > 0) {
|
|
24317
|
+
lines.push("", `**Via group \`${g2.groupName}\`**`, "", holderRows(g2.holders));
|
|
24318
|
+
}
|
|
24319
|
+
}
|
|
24320
|
+
if (data.capped) {
|
|
24321
|
+
lines.push("", `Showing ${data.returned} of ${data.totalAssignees} holder(s)` + (data.nextAfterId !== void 0 ? ` \u2014 pass \`afterId: "${data.nextAfterId}"\` for the next page.` : "."));
|
|
24322
|
+
}
|
|
24323
|
+
lines.push("", PERMSET_HOLDERS_DISCLOSURE, "", renderTrustFooter(data.trust));
|
|
24324
|
+
return lines.join("\n");
|
|
24325
|
+
};
|
|
24326
|
+
aggregateNumber = (records, alias) => {
|
|
24327
|
+
const row = records[0];
|
|
24328
|
+
if (row === void 0)
|
|
24329
|
+
return 0;
|
|
24330
|
+
return Number(row[alias] ?? row["expr0"] ?? 0);
|
|
24331
|
+
};
|
|
24332
|
+
livePermsetHoldersHandler = async (ctx, input2, exec = nodeExecFile2) => {
|
|
24333
|
+
const gate = await gateLive(ctx, input2);
|
|
24334
|
+
if (!gate.ok)
|
|
24335
|
+
return gate;
|
|
24336
|
+
const org = gate.value;
|
|
24337
|
+
const queriedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24338
|
+
const limit = input2.limit ?? DEFAULT_HOLDER_ROWS;
|
|
24339
|
+
const groupBy = input2.groupBy ?? "profile";
|
|
24340
|
+
const includeViaGroups = input2.includeViaGroups ?? true;
|
|
24341
|
+
const nowLiteral = soqlDateTime(/* @__PURE__ */ new Date());
|
|
24342
|
+
const resolved = await resolveHolderTarget(org, input2.name, input2.kind, exec);
|
|
24343
|
+
if (!resolved.ok)
|
|
24344
|
+
return resolved;
|
|
24345
|
+
const target = resolved.value;
|
|
24346
|
+
if (input2.afterId !== void 0 && !/^[a-zA-Z0-9]{15,18}$/.test(input2.afterId)) {
|
|
24347
|
+
return err({
|
|
24348
|
+
kind: "invalid-query",
|
|
24349
|
+
message: "afterId must be a Salesforce record Id (the `nextAfterId` from the previous page).",
|
|
24350
|
+
path: "afterId"
|
|
24351
|
+
});
|
|
24352
|
+
}
|
|
24353
|
+
const afterClause = input2.afterId !== void 0 ? ` AND Id > '${input2.afterId}'` : "";
|
|
24354
|
+
if (target.kind === "profile") {
|
|
24355
|
+
const activeClause2 = input2.includeInactiveAssignees ? "" : " AND IsActive = true";
|
|
24356
|
+
const where2 = `ProfileId = '${target.id}'${activeClause2}`;
|
|
24357
|
+
const totalQ2 = await liveQuery(org, `SELECT COUNT() FROM User WHERE ${where2}`, exec);
|
|
24358
|
+
if (!totalQ2.available)
|
|
24359
|
+
return err(UNAVAILABLE_ERROR("User", org, totalQ2.reason));
|
|
24360
|
+
const totalAssignees2 = totalQ2.total;
|
|
24361
|
+
let activeAssignees2 = totalAssignees2;
|
|
24362
|
+
if (input2.includeInactiveAssignees) {
|
|
24363
|
+
const activeQ = await liveQuery(org, `SELECT COUNT() FROM User WHERE ProfileId = '${target.id}' AND IsActive = true`, exec);
|
|
24364
|
+
if (!activeQ.available)
|
|
24365
|
+
return err(UNAVAILABLE_ERROR("User", org, activeQ.reason));
|
|
24366
|
+
activeAssignees2 = activeQ.total;
|
|
24367
|
+
}
|
|
24368
|
+
const detailQ2 = await liveQuery(org, `SELECT Id, Name, Username, IsActive, Profile.Name FROM User WHERE ${where2}${afterClause} ORDER BY Id LIMIT ${limit}`, exec);
|
|
24369
|
+
if (!detailQ2.available)
|
|
24370
|
+
return err(UNAVAILABLE_ERROR("User", org, detailQ2.reason));
|
|
24371
|
+
const holders = detailQ2.records.map((r2) => ({
|
|
24372
|
+
assignmentId: null,
|
|
24373
|
+
userId: String(r2.Id ?? ""),
|
|
24374
|
+
name: String(r2.Name ?? ""),
|
|
24375
|
+
username: String(r2.Username ?? ""),
|
|
24376
|
+
isActive: r2.IsActive === true,
|
|
24377
|
+
profileName: r2.Profile?.Name ?? null,
|
|
24378
|
+
expirationDate: null,
|
|
24379
|
+
viaGroup: null
|
|
24380
|
+
}));
|
|
24381
|
+
return ok({
|
|
24382
|
+
data: fitPermsetHolders({
|
|
24383
|
+
target,
|
|
24384
|
+
totalAssignees: totalAssignees2,
|
|
24385
|
+
activeAssignees: activeAssignees2,
|
|
24386
|
+
expiredExcluded: 0,
|
|
24387
|
+
trust: liveTrust(queriedAt)
|
|
24388
|
+
}, holders, [], void 0),
|
|
24389
|
+
vaultState: livePlaneVaultState(ctx)
|
|
24390
|
+
});
|
|
24391
|
+
}
|
|
24392
|
+
let viaGroups = [];
|
|
24393
|
+
if (target.kind === "permissionSet" && includeViaGroups) {
|
|
24394
|
+
const psgcQ = await liveQuery(org, `SELECT PermissionSetGroupId, PermissionSetGroup.DeveloperName FROM PermissionSetGroupComponent WHERE PermissionSetId = '${target.id}'`, exec);
|
|
24395
|
+
if (!psgcQ.available) {
|
|
24396
|
+
return err(UNAVAILABLE_ERROR("PermissionSetGroupComponent", org, psgcQ.reason));
|
|
24397
|
+
}
|
|
24398
|
+
viaGroups = psgcQ.records.map((r2) => {
|
|
24399
|
+
const row = r2;
|
|
24400
|
+
return {
|
|
24401
|
+
id: String(row.PermissionSetGroupId ?? ""),
|
|
24402
|
+
name: String(row.PermissionSetGroup?.DeveloperName ?? row.PermissionSetGroupId ?? "")
|
|
24403
|
+
};
|
|
24404
|
+
});
|
|
24405
|
+
}
|
|
24406
|
+
const scope = target.kind === "permissionSetGroup" ? `PermissionSetGroupId = '${target.id}'` : viaGroups.length > 0 ? `((PermissionSetId = '${target.id}' AND PermissionSetGroupId = null) OR PermissionSetGroupId IN (${viaGroups.map((g2) => `'${g2.id}'`).join(", ")}))` : `(PermissionSetId = '${target.id}' AND PermissionSetGroupId = null)`;
|
|
24407
|
+
const activeClause = input2.includeInactiveAssignees ? "" : " AND Assignee.IsActive = true";
|
|
24408
|
+
const notExpiredClause = ` AND (ExpirationDate = null OR ExpirationDate >= ${nowLiteral})`;
|
|
24409
|
+
const where = `${scope}${activeClause}${notExpiredClause}`;
|
|
24410
|
+
const totalQ = await liveQuery(org, `SELECT COUNT_DISTINCT(AssigneeId) total FROM PermissionSetAssignment WHERE ${where}`, exec);
|
|
24411
|
+
if (!totalQ.available)
|
|
24412
|
+
return err(UNAVAILABLE_ERROR("PermissionSetAssignment", org, totalQ.reason));
|
|
24413
|
+
const totalAssignees = aggregateNumber(totalQ.records, "total");
|
|
24414
|
+
let activeAssignees = totalAssignees;
|
|
24415
|
+
if (input2.includeInactiveAssignees) {
|
|
24416
|
+
const activeQ = await liveQuery(org, `SELECT COUNT_DISTINCT(AssigneeId) total FROM PermissionSetAssignment WHERE ${scope} AND Assignee.IsActive = true${notExpiredClause}`, exec);
|
|
24417
|
+
if (!activeQ.available)
|
|
24418
|
+
return err(UNAVAILABLE_ERROR("PermissionSetAssignment", org, activeQ.reason));
|
|
24419
|
+
activeAssignees = aggregateNumber(activeQ.records, "total");
|
|
24420
|
+
}
|
|
24421
|
+
const expiredQ = await liveQuery(org, `SELECT COUNT() FROM PermissionSetAssignment WHERE ${scope}${activeClause} AND ExpirationDate < ${nowLiteral}`, exec);
|
|
24422
|
+
const expiredExcluded = expiredQ.available ? expiredQ.total : 0;
|
|
24423
|
+
let buckets;
|
|
24424
|
+
if (groupBy === "profile") {
|
|
24425
|
+
const bucketQ = await liveQuery(org, `SELECT Assignee.ProfileId pid, MAX(Assignee.Profile.Name) pname, COUNT(Id) holders FROM PermissionSetAssignment WHERE ${where} GROUP BY Assignee.ProfileId ORDER BY COUNT(Id) DESC LIMIT ${MAX_HOLDER_BUCKETS}`, exec);
|
|
24426
|
+
if (bucketQ.available) {
|
|
24427
|
+
buckets = bucketQ.records.map((r2) => {
|
|
24428
|
+
const row = r2;
|
|
24429
|
+
return {
|
|
24430
|
+
profileId: row.pid === void 0 || row.pid === null ? null : String(row.pid),
|
|
24431
|
+
profileName: row.pname === void 0 || row.pname === null ? null : String(row.pname),
|
|
24432
|
+
holders: Number(row.holders ?? row.expr1 ?? 0)
|
|
24433
|
+
};
|
|
24434
|
+
});
|
|
24435
|
+
}
|
|
24436
|
+
}
|
|
24437
|
+
const detailQ = await liveQuery(org, `SELECT Id, AssigneeId, Assignee.Name, Assignee.Username, Assignee.IsActive, Assignee.Profile.Name, ExpirationDate, PermissionSetGroupId, PermissionSetGroup.DeveloperName FROM PermissionSetAssignment WHERE ${where}${afterClause} ORDER BY Id LIMIT ${limit}`, exec);
|
|
24438
|
+
if (!detailQ.available)
|
|
24439
|
+
return err(UNAVAILABLE_ERROR("PermissionSetAssignment", org, detailQ.reason));
|
|
24440
|
+
const rows = detailQ.records.map(toPermsetHolder);
|
|
24441
|
+
return ok({
|
|
24442
|
+
data: fitPermsetHolders({
|
|
24443
|
+
target,
|
|
24444
|
+
totalAssignees,
|
|
24445
|
+
activeAssignees,
|
|
24446
|
+
expiredExcluded,
|
|
24447
|
+
trust: liveTrust(queriedAt)
|
|
24448
|
+
}, rows, viaGroups.map((g2) => g2.name), buckets),
|
|
24449
|
+
vaultState: livePlaneVaultState(ctx)
|
|
24450
|
+
});
|
|
24451
|
+
};
|
|
24452
|
+
splitHolders = (rows, groupNames) => {
|
|
24453
|
+
const direct = rows.filter((h2) => h2.viaGroup === null);
|
|
24454
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
24455
|
+
for (const h2 of rows) {
|
|
24456
|
+
if (h2.viaGroup === null)
|
|
24457
|
+
continue;
|
|
24458
|
+
const list = byGroup.get(h2.viaGroup) ?? [];
|
|
24459
|
+
list.push(h2);
|
|
24460
|
+
byGroup.set(h2.viaGroup, list);
|
|
24461
|
+
}
|
|
24462
|
+
const ordered = [
|
|
24463
|
+
...groupNames.filter((g2) => byGroup.has(g2)),
|
|
24464
|
+
...[...byGroup.keys()].filter((g2) => !groupNames.includes(g2))
|
|
24465
|
+
];
|
|
24466
|
+
return {
|
|
24467
|
+
direct,
|
|
24468
|
+
via: ordered.map((g2) => ({ groupName: g2, holders: byGroup.get(g2) ?? [] }))
|
|
24469
|
+
};
|
|
24470
|
+
};
|
|
24471
|
+
fitPermsetHolders = (base, allRows, groupNames, buckets) => {
|
|
24472
|
+
let slice = allRows;
|
|
24473
|
+
let byteTrimmed = false;
|
|
24474
|
+
for (; ; ) {
|
|
24475
|
+
const returned = slice.length;
|
|
24476
|
+
const distinctReturned = new Set(slice.map((h2) => h2.userId)).size;
|
|
24477
|
+
const capped = byteTrimmed || base.totalAssignees > distinctReturned;
|
|
24478
|
+
const last = slice[slice.length - 1];
|
|
24479
|
+
const nextAfterId = capped && last !== void 0 ? last.assignmentId ?? last.userId : void 0;
|
|
24480
|
+
const { direct, via } = splitHolders(slice, groupNames);
|
|
24481
|
+
const shape = {
|
|
24482
|
+
...base,
|
|
24483
|
+
returned,
|
|
24484
|
+
capped,
|
|
24485
|
+
...nextAfterId !== void 0 ? { nextAfterId } : {},
|
|
24486
|
+
...buckets !== void 0 ? { buckets } : {},
|
|
24487
|
+
directHolders: direct,
|
|
24488
|
+
...groupNames.length > 0 || via.length > 0 ? { viaGroupHolders: via } : {},
|
|
24489
|
+
...byteTrimmed ? {
|
|
24490
|
+
note: `Detail rows trimmed to ${returned} to stay within the response size limit; totalAssignees (${base.totalAssignees}) is the true count \u2014 page on with afterId.`
|
|
24491
|
+
} : {}
|
|
24492
|
+
};
|
|
24493
|
+
const rendered = renderPermsetHoldersMarkdown(shape);
|
|
24494
|
+
const bytes = Buffer.byteLength(JSON.stringify({ ...shape, rendered }), "utf8");
|
|
24495
|
+
if (bytes <= HOLDERS_BYTE_BUDGET || slice.length <= 1) {
|
|
24496
|
+
return { ...shape, rendered };
|
|
24497
|
+
}
|
|
24498
|
+
slice = slice.slice(0, Math.max(1, Math.floor(slice.length * 0.85)));
|
|
24499
|
+
byteTrimmed = true;
|
|
24500
|
+
}
|
|
24501
|
+
};
|
|
24502
|
+
MAX_ZOMBIE_ROWS = 500;
|
|
24503
|
+
DEFAULT_ZOMBIE_ROWS = 100;
|
|
24504
|
+
ZOMBIE_BYTE_BUDGET = 36e3;
|
|
24505
|
+
ZOMBIE_CLIENT_DIFF_CAP = 2e3;
|
|
24506
|
+
ZOMBIE_ACCOUNTS_DISCLOSURE = 'A "zombie" here still holds every permission its PROFILE grants \u2014 this tool reports "no permission-set/PSG assignments", NOT "no access". Read-only; point-in-time as of queriedAt.';
|
|
24507
|
+
liveZombieAccountsInputSchema = liveEnabledSchema.extend({
|
|
24508
|
+
/** Additionally require last login older than N days (default 0 = ignore
|
|
24509
|
+
* login age; dormancy alone is sfi.live_inactive_users' job). */
|
|
24510
|
+
minDaysInactive: z2.number().int().min(0).max(3650).optional(),
|
|
24511
|
+
/** Include non-Standard user types (default false — Standard/human only). */
|
|
24512
|
+
includeAllUserTypes: z2.boolean().optional(),
|
|
24513
|
+
/** Max detail rows (default 100, hard cap 500); byte budget may trim further.
|
|
24514
|
+
* `totalZombies` is always the true count. */
|
|
24515
|
+
limit: z2.number().int().min(1).max(MAX_ZOMBIE_ROWS).optional(),
|
|
24516
|
+
orgAlias: z2.string().min(1).optional()
|
|
24517
|
+
});
|
|
24518
|
+
toZombieUser = (r2) => ({
|
|
24519
|
+
id: String(r2.Id ?? ""),
|
|
24520
|
+
name: String(r2.Name ?? ""),
|
|
24521
|
+
username: String(r2.Username ?? ""),
|
|
24522
|
+
profileName: r2.Profile?.Name ?? null,
|
|
24523
|
+
lastLoginDate: r2.LastLoginDate ?? null,
|
|
24524
|
+
neverLoggedIn: (r2.LastLoginDate ?? null) === null
|
|
24525
|
+
});
|
|
24526
|
+
renderZombieAccountsMarkdown = (data) => {
|
|
24527
|
+
const head = `**${data.totalZombies.toLocaleString("en-US")}** active ${data.criteria.userTypeFilter === "Standard" ? "Standard " : ""}user(s) have login access but ZERO permission-set/PSG assignments` + (data.criteria.cutoff !== null ? ` and no login within ${data.criteria.minDaysInactive} days` : "") + ".";
|
|
24528
|
+
const table = data.users.length === 0 ? "" : `
|
|
24529
|
+
|
|
24530
|
+
${mdTable(["User", "Username", "Profile", "Last login"], data.users.map((u2) => [
|
|
24531
|
+
u2.name,
|
|
24532
|
+
u2.username,
|
|
24533
|
+
u2.profileName ?? "\u2014",
|
|
24534
|
+
u2.neverLoggedIn ? "never" : u2.lastLoginDate ?? "\u2014"
|
|
24535
|
+
]))}`;
|
|
24536
|
+
const cappedLine = data.capped ? `
|
|
24537
|
+
|
|
24538
|
+
Showing ${data.returned} of ${data.totalZombies}.` : "";
|
|
24539
|
+
const methodLine = data.method === "client-diff" ? `
|
|
24540
|
+
|
|
24541
|
+
Method: client-diff \u2014 the org rejected the single anti-join SOQL, so this was computed by diffing two bounded queries (scan cap ${ZOMBIE_CLIENT_DIFF_CAP} rows each).` : "";
|
|
24542
|
+
return `${head}${table}${cappedLine}${methodLine}
|
|
24543
|
+
|
|
24544
|
+
${data.disclosure}
|
|
24545
|
+
|
|
24546
|
+
${renderTrustFooter(data.trust)}`;
|
|
24547
|
+
};
|
|
24548
|
+
fitZombieUsers = (base, allUsers, extraNote) => {
|
|
24549
|
+
let slice = allUsers;
|
|
24550
|
+
let byteTrimmed = false;
|
|
24551
|
+
for (; ; ) {
|
|
24552
|
+
const returned = slice.length;
|
|
24553
|
+
const capped = byteTrimmed || base.totalZombies > returned;
|
|
24554
|
+
const noteText = [
|
|
24555
|
+
...extraNote !== void 0 ? [extraNote] : [],
|
|
24556
|
+
...byteTrimmed ? [
|
|
24557
|
+
`Detail rows trimmed to ${returned} to stay within the response size limit; totalZombies (${base.totalZombies}) is the true count.`
|
|
24558
|
+
] : []
|
|
24559
|
+
].join(" ");
|
|
24560
|
+
const shape = {
|
|
24561
|
+
...base,
|
|
24562
|
+
returned,
|
|
24563
|
+
capped,
|
|
24564
|
+
users: slice,
|
|
24565
|
+
...noteText.length > 0 ? { note: noteText } : {}
|
|
24566
|
+
};
|
|
24567
|
+
const rendered = renderZombieAccountsMarkdown(shape);
|
|
24568
|
+
const bytes = Buffer.byteLength(JSON.stringify({ ...shape, rendered }), "utf8");
|
|
24569
|
+
if (bytes <= ZOMBIE_BYTE_BUDGET || slice.length <= 1) {
|
|
24570
|
+
return { ...shape, rendered };
|
|
24571
|
+
}
|
|
24572
|
+
slice = slice.slice(0, Math.max(1, Math.floor(slice.length * 0.85)));
|
|
24573
|
+
byteTrimmed = true;
|
|
24574
|
+
}
|
|
24575
|
+
};
|
|
24576
|
+
liveZombieAccountsHandler = async (ctx, input2, exec = nodeExecFile2) => {
|
|
24577
|
+
const gate = await gateLive(ctx, input2);
|
|
24578
|
+
if (!gate.ok)
|
|
24579
|
+
return gate;
|
|
24580
|
+
const org = gate.value;
|
|
24581
|
+
const queriedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24582
|
+
const minDaysInactive = input2.minDaysInactive ?? 0;
|
|
24583
|
+
const limit = input2.limit ?? DEFAULT_ZOMBIE_ROWS;
|
|
24584
|
+
const cutoff = minDaysInactive > 0 ? daysAgoSoql(minDaysInactive) : null;
|
|
24585
|
+
const userTypeClause = input2.includeAllUserTypes ? "" : ` AND UserType = 'Standard'`;
|
|
24586
|
+
const loginClause = cutoff !== null ? ` AND (LastLoginDate < ${cutoff} OR LastLoginDate = null)` : "";
|
|
24587
|
+
const baseWhere = `IsActive = true${userTypeClause}${loginClause}`;
|
|
24588
|
+
const antiJoin = ` AND Id NOT IN (SELECT AssigneeId FROM PermissionSetAssignment WHERE PermissionSet.IsOwnedByProfile = false)`;
|
|
24589
|
+
const criteria = {
|
|
24590
|
+
minDaysInactive,
|
|
24591
|
+
cutoff,
|
|
24592
|
+
userTypeFilter: input2.includeAllUserTypes ? "all" : "Standard"
|
|
24593
|
+
};
|
|
24594
|
+
const detailSelect = `SELECT Id, Name, Username, Profile.Name, LastLoginDate FROM User`;
|
|
24595
|
+
const countQ = await liveQuery(org, `SELECT COUNT() FROM User WHERE ${baseWhere}${antiJoin}`, exec);
|
|
24596
|
+
if (countQ.available) {
|
|
24597
|
+
const detailQ = await liveQuery(org, `${detailSelect} WHERE ${baseWhere}${antiJoin} ORDER BY LastLoginDate ASC NULLS FIRST LIMIT ${limit}`, exec);
|
|
24598
|
+
if (!detailQ.available)
|
|
24599
|
+
return err(UNAVAILABLE_ERROR("User", org, detailQ.reason));
|
|
24600
|
+
const users = detailQ.records.map(toZombieUser);
|
|
24601
|
+
return ok({
|
|
24602
|
+
data: fitZombieUsers({
|
|
24603
|
+
criteria,
|
|
24604
|
+
totalZombies: countQ.total,
|
|
24605
|
+
method: "anti-join",
|
|
24606
|
+
disclosure: ZOMBIE_ACCOUNTS_DISCLOSURE,
|
|
24607
|
+
trust: liveTrust(queriedAt)
|
|
24608
|
+
}, users, void 0),
|
|
24609
|
+
vaultState: livePlaneVaultState(ctx)
|
|
24610
|
+
});
|
|
24611
|
+
}
|
|
24612
|
+
if (isBudgetExhaustedReason(countQ.reason)) {
|
|
24613
|
+
return err({ kind: "invalid-query", message: redactSecrets(countQ.reason ?? "live-query budget exhausted") });
|
|
24614
|
+
}
|
|
24615
|
+
const usersQ = await liveQuery(org, `${detailSelect} WHERE ${baseWhere} ORDER BY LastLoginDate ASC NULLS FIRST LIMIT ${ZOMBIE_CLIENT_DIFF_CAP}`, exec);
|
|
24616
|
+
if (!usersQ.available)
|
|
24617
|
+
return err(UNAVAILABLE_ERROR("User", org, usersQ.reason));
|
|
24618
|
+
const assigneesQ = await liveQuery(org, `SELECT AssigneeId FROM PermissionSetAssignment WHERE PermissionSet.IsOwnedByProfile = false GROUP BY AssigneeId LIMIT ${ZOMBIE_CLIENT_DIFF_CAP}`, exec);
|
|
24619
|
+
if (!assigneesQ.available) {
|
|
24620
|
+
return err(UNAVAILABLE_ERROR("PermissionSetAssignment", org, assigneesQ.reason));
|
|
24621
|
+
}
|
|
24622
|
+
const assigneeIds = new Set(assigneesQ.records.map((r2) => String(r2.AssigneeId ?? "")));
|
|
24623
|
+
const zombies = usersQ.records.map(toZombieUser).filter((u2) => !assigneeIds.has(u2.id) && !assigneeIds.has(u2.id.slice(0, 15)));
|
|
24624
|
+
const scanBounded = usersQ.records.length >= ZOMBIE_CLIENT_DIFF_CAP || assigneesQ.records.length >= ZOMBIE_CLIENT_DIFF_CAP;
|
|
24625
|
+
const extraNote = `Anti-join SOQL was rejected by the org (${redactSecrets(countQ.reason ?? "unknown").slice(0, 120)}); fell back to a client-side diff of two bounded queries.` + (scanBounded ? ` One scan hit its ${ZOMBIE_CLIENT_DIFF_CAP}-row cap, so totalZombies may UNDERCOUNT \u2014 treat it as a lower bound.` : "");
|
|
24626
|
+
return ok({
|
|
24627
|
+
data: fitZombieUsers({
|
|
24628
|
+
criteria,
|
|
24629
|
+
totalZombies: zombies.length,
|
|
24630
|
+
method: "client-diff",
|
|
24631
|
+
disclosure: ZOMBIE_ACCOUNTS_DISCLOSURE,
|
|
24632
|
+
trust: liveTrust(queriedAt)
|
|
24633
|
+
}, zombies.slice(0, limit), extraNote),
|
|
24634
|
+
vaultState: livePlaneVaultState(ctx)
|
|
24635
|
+
});
|
|
24636
|
+
};
|
|
24637
|
+
MAX_MEMBER_ROWS = 500;
|
|
24638
|
+
DEFAULT_MEMBER_ROWS = 100;
|
|
24639
|
+
MEMBERS_BYTE_BUDGET = 36e3;
|
|
24640
|
+
MEMBER_ID_CHUNK = 200;
|
|
24641
|
+
GROUP_MEMBERS_DISCLOSURE = 'Direct membership only by default \u2014 nested public groups and role entries are listed but NOT expanded to users. expandNested: true expands exactly ONE level of nested public groups (stamped expansion: "partial-one-level"); deeper nesting and role-hierarchy subordinates are not enumerated \u2014 never treat this as the full effective membership. Read-only; point-in-time as of queriedAt.';
|
|
24642
|
+
liveGroupMembersInputSchema = liveEnabledSchema.extend({
|
|
24643
|
+
/** Group.DeveloperName or Group.Name (label) — exact match, never a guess. */
|
|
24644
|
+
name: z2.string().min(1),
|
|
24645
|
+
/** What kind of Group `name` names. Default 'auto' matches both Queue and
|
|
24646
|
+
* Regular (public group); an ambiguous name across both is an honest error. */
|
|
24647
|
+
groupType: z2.enum(["Queue", "Regular", "auto"]).optional(),
|
|
24648
|
+
/** Expand exactly ONE level of nested public groups (default false). Role
|
|
24649
|
+
* entries are never expanded. */
|
|
24650
|
+
expandNested: z2.boolean().optional(),
|
|
24651
|
+
/** Max direct GroupMember rows fetched (default 100, hard cap 500); a ~36 KB
|
|
24652
|
+
* byte budget may trim the user page further. `totalDirectMembers` is
|
|
24653
|
+
* always the TRUE count. */
|
|
24654
|
+
limit: z2.number().int().min(1).max(MAX_MEMBER_ROWS).optional(),
|
|
24655
|
+
orgAlias: z2.string().min(1).optional()
|
|
24656
|
+
});
|
|
24657
|
+
idChunks = (ids) => {
|
|
24658
|
+
const chunks = [];
|
|
24659
|
+
for (let i2 = 0; i2 < ids.length; i2 += MEMBER_ID_CHUNK) {
|
|
24660
|
+
chunks.push(ids.slice(i2, i2 + MEMBER_ID_CHUNK));
|
|
24661
|
+
}
|
|
24662
|
+
return chunks;
|
|
24663
|
+
};
|
|
24664
|
+
resolveMemberUsers = async (org, ids, exec) => {
|
|
24665
|
+
const byId = /* @__PURE__ */ new Map();
|
|
24666
|
+
for (const chunk of idChunks(ids)) {
|
|
24667
|
+
const inList = chunk.map((id) => soqlLiteral(id)).join(", ");
|
|
24668
|
+
const q2 = await liveQuery(org, `SELECT Id, Name, Username, IsActive FROM User WHERE Id IN (${inList})`, exec);
|
|
24669
|
+
if (!q2.available)
|
|
24670
|
+
return err(UNAVAILABLE_ERROR("User", org, q2.reason));
|
|
24671
|
+
for (const r2 of q2.records) {
|
|
24672
|
+
const row = r2;
|
|
24673
|
+
const id = String(row.Id ?? "");
|
|
24674
|
+
byId.set(id, {
|
|
24675
|
+
id,
|
|
24676
|
+
name: String(row.Name ?? ""),
|
|
24677
|
+
username: String(row.Username ?? ""),
|
|
24678
|
+
isActive: row.IsActive === true
|
|
24679
|
+
});
|
|
24680
|
+
}
|
|
24681
|
+
}
|
|
24682
|
+
return ok(byId);
|
|
24683
|
+
};
|
|
24684
|
+
renderGroupMembersMarkdown = (data) => {
|
|
24685
|
+
const kind = data.group.type === "Queue" ? "queue" : "public group";
|
|
24686
|
+
const lines = [
|
|
24687
|
+
`### Members of ${kind} \`${data.group.developerName}\``,
|
|
24688
|
+
"",
|
|
24689
|
+
`**${data.totalDirectMembers.toLocaleString("en-US")}** direct member entr${data.totalDirectMembers === 1 ? "y" : "ies"} (${data.users.length} user(s), ${data.nestedGroups.length} nested group(s), ${data.roles.length} role(s) on this page).`
|
|
24690
|
+
];
|
|
24691
|
+
if (data.supportedObjects !== void 0) {
|
|
24692
|
+
lines.push("", data.supportedObjects.length > 0 ? `**Can own:** ${data.supportedObjects.join(", ")}` : "**Can own:** (no QueueSobject rows \u2014 this queue supports no objects)");
|
|
24693
|
+
}
|
|
24694
|
+
if (data.users.length > 0) {
|
|
24695
|
+
lines.push("", "**Users**", "", mdTable(["User", "Username", "Active"], data.users.map((u2) => [u2.name, u2.username, u2.isActive ? "yes" : "no"])));
|
|
24696
|
+
}
|
|
24697
|
+
for (const g2 of data.nestedGroups) {
|
|
24698
|
+
lines.push("", `**Nested group \`${g2.name}\`** (${g2.type}${g2.members === void 0 ? " \u2014 not expanded" : ""})`);
|
|
24699
|
+
if (g2.members !== void 0) {
|
|
24700
|
+
lines.push("", g2.members.length > 0 ? mdTable(["User", "Username", "Active"], g2.members.map((u2) => [u2.name, u2.username, u2.isActive ? "yes" : "no"])) : "(no direct users)");
|
|
24701
|
+
if ((g2.unexpandedNestedCount ?? 0) > 0) {
|
|
24702
|
+
lines.push("", `${g2.unexpandedNestedCount} nested group/role member(s) inside \`${g2.name}\` were NOT expanded (one-level limit).`);
|
|
24703
|
+
}
|
|
24704
|
+
}
|
|
24705
|
+
}
|
|
24706
|
+
if (data.roles.length > 0) {
|
|
24707
|
+
lines.push("", "**Roles** (never expanded to users)", "", mdTable(["Role", "Includes subordinates"], data.roles.map((r2) => [r2.roleName ?? r2.id, r2.includesSubordinates ? "yes" : "no"])));
|
|
24708
|
+
}
|
|
24709
|
+
lines.push("", data.vaultDeclaredMemberCount === null ? "Vault cross-check: no declared member count in the vault for this group (membership is runtime data)." : `Vault cross-check: vault declared **${data.vaultDeclaredMemberCount}** member(s) at last refresh vs **${data.liveDirectMemberCount}** live direct member(s) \u2014 drift: ${data.drift ? "YES" : "no"}.`);
|
|
24710
|
+
if (data.capped) {
|
|
24711
|
+
lines.push("", `Showing ${data.returned} of ${data.totalDirectMembers} direct member(s).`);
|
|
24712
|
+
}
|
|
24713
|
+
lines.push("", data.disclosure, "", renderTrustFooter(data.trust));
|
|
24714
|
+
return lines.join("\n");
|
|
24715
|
+
};
|
|
24716
|
+
fitGroupMembers = (base, allUsers, pageEntryCount, extraNote) => {
|
|
24717
|
+
let slice = allUsers;
|
|
24718
|
+
let byteTrimmed = false;
|
|
24719
|
+
for (; ; ) {
|
|
24720
|
+
const returned = pageEntryCount - (allUsers.length - slice.length);
|
|
24721
|
+
const capped = byteTrimmed || base.totalDirectMembers > pageEntryCount;
|
|
24722
|
+
const noteText = [
|
|
24723
|
+
...extraNote !== void 0 ? [extraNote] : [],
|
|
24724
|
+
...byteTrimmed ? [
|
|
24725
|
+
`User rows trimmed to ${slice.length} to stay within the response size limit; totalDirectMembers (${base.totalDirectMembers}) is the true count.`
|
|
24726
|
+
] : []
|
|
24727
|
+
].join(" ");
|
|
24728
|
+
const shape = {
|
|
24729
|
+
...base,
|
|
24730
|
+
returned,
|
|
24731
|
+
capped,
|
|
24732
|
+
users: slice,
|
|
24733
|
+
...noteText.length > 0 ? { note: noteText } : {}
|
|
24734
|
+
};
|
|
24735
|
+
const rendered = renderGroupMembersMarkdown(shape);
|
|
24736
|
+
const bytes = Buffer.byteLength(JSON.stringify({ ...shape, rendered }), "utf8");
|
|
24737
|
+
if (bytes <= MEMBERS_BYTE_BUDGET || slice.length <= 1) {
|
|
24738
|
+
return { ...shape, rendered };
|
|
24739
|
+
}
|
|
24740
|
+
slice = slice.slice(0, Math.max(1, Math.floor(slice.length * 0.85)));
|
|
24741
|
+
byteTrimmed = true;
|
|
24742
|
+
}
|
|
24743
|
+
};
|
|
24744
|
+
liveGroupMembersHandler = async (ctx, input2, exec = nodeExecFile2) => {
|
|
24745
|
+
const gate = await gateLive(ctx, input2);
|
|
24746
|
+
if (!gate.ok)
|
|
24747
|
+
return gate;
|
|
24748
|
+
const org = gate.value;
|
|
24749
|
+
const queriedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24750
|
+
const limit = input2.limit ?? DEFAULT_MEMBER_ROWS;
|
|
24751
|
+
const lit = soqlLiteral(input2.name);
|
|
24752
|
+
const typeClause = input2.groupType === void 0 || input2.groupType === "auto" ? `Type IN ('Queue', 'Regular')` : `Type = '${input2.groupType}'`;
|
|
24753
|
+
const resolveQ = await liveQuery(org, `SELECT Id, Name, DeveloperName, Type FROM Group WHERE (DeveloperName = ${lit} OR Name = ${lit}) AND ${typeClause} LIMIT 3`, exec);
|
|
24754
|
+
if (!resolveQ.available)
|
|
24755
|
+
return err(UNAVAILABLE_ERROR("Group", org, resolveQ.reason));
|
|
24756
|
+
if (resolveQ.records.length === 0) {
|
|
24757
|
+
return err({
|
|
24758
|
+
kind: "component-not-found",
|
|
24759
|
+
message: `No queue or public group named '${input2.name}' in the live org (probed Group by exact DeveloperName/Name${input2.groupType && input2.groupType !== "auto" ? `, Type ${input2.groupType}` : ""}). Check the API name \u2014 sfi.resolve can fix typos against the vault.`
|
|
24760
|
+
});
|
|
24761
|
+
}
|
|
24762
|
+
if (resolveQ.records.length > 1) {
|
|
24763
|
+
const kinds = resolveQ.records.map((r2) => String(r2.Type ?? "?")).join(" + ");
|
|
24764
|
+
return err({
|
|
24765
|
+
kind: "invalid-query",
|
|
24766
|
+
message: `'${input2.name}' matches ${resolveQ.records.length} Group records in the live org (${kinds}) \u2014 refusing to guess. Pass groupType ('Queue' | 'Regular') or the exact DeveloperName.`
|
|
24767
|
+
});
|
|
24768
|
+
}
|
|
24769
|
+
const groupRow = resolveQ.records[0];
|
|
24770
|
+
const group = {
|
|
24771
|
+
id: String(groupRow.Id ?? ""),
|
|
24772
|
+
name: String(groupRow.Name ?? groupRow.DeveloperName ?? input2.name),
|
|
24773
|
+
developerName: String(groupRow.DeveloperName ?? input2.name),
|
|
24774
|
+
type: groupRow.Type === "Queue" ? "Queue" : "Regular"
|
|
24775
|
+
};
|
|
24776
|
+
const countQ = await liveQuery(org, `SELECT COUNT() FROM GroupMember WHERE GroupId = '${group.id}'`, exec);
|
|
24777
|
+
if (!countQ.available)
|
|
24778
|
+
return err(UNAVAILABLE_ERROR("GroupMember", org, countQ.reason));
|
|
24779
|
+
const totalDirectMembers = countQ.total;
|
|
24780
|
+
const membersQ = await liveQuery(org, `SELECT Id, UserOrGroupId FROM GroupMember WHERE GroupId = '${group.id}' ORDER BY Id LIMIT ${limit}`, exec);
|
|
24781
|
+
if (!membersQ.available)
|
|
24782
|
+
return err(UNAVAILABLE_ERROR("GroupMember", org, membersQ.reason));
|
|
24783
|
+
const memberIds = membersQ.records.map((r2) => String(r2.UserOrGroupId ?? ""));
|
|
24784
|
+
const directUserIds = memberIds.filter((id) => id.startsWith("005"));
|
|
24785
|
+
const nestedGroupIds = memberIds.filter((id) => id.startsWith("00G"));
|
|
24786
|
+
const unclassified = memberIds.filter((id) => !id.startsWith("005") && !id.startsWith("00G"));
|
|
24787
|
+
let nestedRows = [];
|
|
24788
|
+
if (nestedGroupIds.length > 0) {
|
|
24789
|
+
for (const chunk of idChunks(nestedGroupIds)) {
|
|
24790
|
+
const inList = chunk.map((id) => soqlLiteral(id)).join(", ");
|
|
24791
|
+
const q2 = await liveQuery(org, `SELECT Id, Name, DeveloperName, Type, RelatedId FROM Group WHERE Id IN (${inList})`, exec);
|
|
24792
|
+
if (!q2.available)
|
|
24793
|
+
return err(UNAVAILABLE_ERROR("Group", org, q2.reason));
|
|
24794
|
+
nestedRows = nestedRows.concat(q2.records);
|
|
24795
|
+
}
|
|
24796
|
+
}
|
|
24797
|
+
const roleRows = nestedRows.filter((r2) => String(r2.Type ?? "").startsWith("Role"));
|
|
24798
|
+
const publicNestedRows = nestedRows.filter((r2) => !String(r2.Type ?? "").startsWith("Role"));
|
|
24799
|
+
const roleNameById = /* @__PURE__ */ new Map();
|
|
24800
|
+
const relatedIds = roleRows.map((r2) => String(r2.RelatedId ?? "")).filter((id) => id.length > 0);
|
|
24801
|
+
if (relatedIds.length > 0) {
|
|
24802
|
+
const inList = relatedIds.map((id) => soqlLiteral(id)).join(", ");
|
|
24803
|
+
const q2 = await liveQuery(org, `SELECT Id, Name FROM UserRole WHERE Id IN (${inList})`, exec);
|
|
24804
|
+
if (!q2.available && isBudgetExhaustedReason(q2.reason)) {
|
|
24805
|
+
return err({ kind: "invalid-query", message: redactSecrets(q2.reason ?? "live-query budget exhausted") });
|
|
24806
|
+
}
|
|
24807
|
+
if (q2.available) {
|
|
24808
|
+
for (const r2 of q2.records) {
|
|
24809
|
+
const row = r2;
|
|
24810
|
+
roleNameById.set(String(row.Id ?? ""), String(row.Name ?? ""));
|
|
24811
|
+
}
|
|
24812
|
+
}
|
|
24813
|
+
}
|
|
24814
|
+
const roles = roleRows.map((r2) => {
|
|
24815
|
+
const relatedId = String(r2.RelatedId ?? "");
|
|
24816
|
+
return {
|
|
24817
|
+
id: String(r2.Id ?? ""),
|
|
24818
|
+
roleName: roleNameById.get(relatedId) ?? null,
|
|
24819
|
+
type: String(r2.Type ?? "Role"),
|
|
24820
|
+
includesSubordinates: String(r2.Type ?? "").startsWith("RoleAndSubordinates")
|
|
24821
|
+
};
|
|
24822
|
+
});
|
|
24823
|
+
const expandNested = input2.expandNested === true;
|
|
24824
|
+
const nestedMembersByGroup = /* @__PURE__ */ new Map();
|
|
24825
|
+
if (expandNested && publicNestedRows.length > 0) {
|
|
24826
|
+
const inList = publicNestedRows.map((r2) => soqlLiteral(String(r2.Id ?? ""))).join(", ");
|
|
24827
|
+
const q2 = await liveQuery(org, `SELECT GroupId, UserOrGroupId FROM GroupMember WHERE GroupId IN (${inList}) ORDER BY Id LIMIT ${MAX_MEMBER_ROWS}`, exec);
|
|
24828
|
+
if (!q2.available)
|
|
24829
|
+
return err(UNAVAILABLE_ERROR("GroupMember", org, q2.reason));
|
|
24830
|
+
for (const r2 of q2.records) {
|
|
24831
|
+
const row = r2;
|
|
24832
|
+
const gid = String(row.GroupId ?? "");
|
|
24833
|
+
const list = nestedMembersByGroup.get(gid) ?? [];
|
|
24834
|
+
list.push(String(row.UserOrGroupId ?? ""));
|
|
24835
|
+
nestedMembersByGroup.set(gid, list);
|
|
24836
|
+
}
|
|
24837
|
+
}
|
|
24838
|
+
const nestedUserIds = [...nestedMembersByGroup.values()].flat().filter((id) => id.startsWith("005"));
|
|
24839
|
+
const allUserIds = [.../* @__PURE__ */ new Set([...directUserIds, ...nestedUserIds])];
|
|
24840
|
+
const usersR = await resolveMemberUsers(org, allUserIds, exec);
|
|
24841
|
+
if (!usersR.ok)
|
|
24842
|
+
return usersR;
|
|
24843
|
+
const userById = usersR.value;
|
|
24844
|
+
const toUser = (id) => userById.get(id) ?? userById.get(id.slice(0, 15)) ?? { id, name: "", username: "", isActive: false };
|
|
24845
|
+
const nestedGroups = publicNestedRows.map((r2) => {
|
|
24846
|
+
const id = String(r2.Id ?? "");
|
|
24847
|
+
const base = {
|
|
24848
|
+
id,
|
|
24849
|
+
name: String(r2.DeveloperName ?? r2.Name ?? id),
|
|
24850
|
+
type: String(r2.Type ?? "Regular")
|
|
24851
|
+
};
|
|
24852
|
+
if (!expandNested)
|
|
24853
|
+
return base;
|
|
24854
|
+
const memberIdsOfGroup = nestedMembersByGroup.get(id) ?? [];
|
|
24855
|
+
const userMembers = memberIdsOfGroup.filter((m2) => m2.startsWith("005")).map(toUser);
|
|
24856
|
+
const unexpanded = memberIdsOfGroup.length - userMembers.length;
|
|
24857
|
+
return {
|
|
24858
|
+
...base,
|
|
24859
|
+
members: userMembers,
|
|
24860
|
+
...unexpanded > 0 ? { unexpandedNestedCount: unexpanded } : {}
|
|
24861
|
+
};
|
|
24862
|
+
});
|
|
24863
|
+
let supportedObjects;
|
|
24864
|
+
if (group.type === "Queue") {
|
|
24865
|
+
const q2 = await liveQuery(org, `SELECT SobjectType FROM QueueSobject WHERE QueueId = '${group.id}'`, exec);
|
|
24866
|
+
if (!q2.available && isBudgetExhaustedReason(q2.reason)) {
|
|
24867
|
+
return err({ kind: "invalid-query", message: redactSecrets(q2.reason ?? "live-query budget exhausted") });
|
|
24868
|
+
}
|
|
24869
|
+
if (q2.available) {
|
|
24870
|
+
supportedObjects = q2.records.map((r2) => String(r2.SobjectType ?? "")).filter((s2) => s2.length > 0).sort();
|
|
24871
|
+
}
|
|
24872
|
+
}
|
|
24873
|
+
let vaultDeclaredMemberCount = null;
|
|
24874
|
+
if (ctx.graph !== void 0 && ctx.graph !== null) {
|
|
24875
|
+
const vaultId = `${group.type === "Queue" ? "Queue" : "Group"}:${group.developerName}`;
|
|
24876
|
+
const nodeR = await getNodeById(ctx.graph, vaultId);
|
|
24877
|
+
if (nodeR.ok && nodeR.value !== null) {
|
|
24878
|
+
const declared = nodeR.value.properties["memberCount"];
|
|
24879
|
+
if (typeof declared === "number")
|
|
24880
|
+
vaultDeclaredMemberCount = declared;
|
|
24881
|
+
}
|
|
24882
|
+
}
|
|
24883
|
+
const drift = vaultDeclaredMemberCount !== null && vaultDeclaredMemberCount !== totalDirectMembers;
|
|
24884
|
+
const extraNote = unclassified.length > 0 ? `${unclassified.length} GroupMember row(s) carry an id prefix that is neither User (005) nor Group (00G) \u2014 listed in no bucket, but counted in totalDirectMembers.` : void 0;
|
|
24885
|
+
return ok({
|
|
24886
|
+
data: fitGroupMembers({
|
|
24887
|
+
group,
|
|
24888
|
+
...supportedObjects !== void 0 ? { supportedObjects } : {},
|
|
24889
|
+
totalDirectMembers,
|
|
24890
|
+
nestedGroups,
|
|
24891
|
+
roles,
|
|
24892
|
+
expansion: expandNested ? "partial-one-level" : "none",
|
|
24893
|
+
vaultDeclaredMemberCount,
|
|
24894
|
+
liveDirectMemberCount: totalDirectMembers,
|
|
24895
|
+
drift,
|
|
24896
|
+
disclosure: GROUP_MEMBERS_DISCLOSURE,
|
|
24897
|
+
trust: liveTrust(queriedAt)
|
|
24898
|
+
}, directUserIds.map(toUser), memberIds.length, extraNote),
|
|
24899
|
+
vaultState: livePlaneVaultState(ctx)
|
|
24900
|
+
});
|
|
24901
|
+
};
|
|
24902
|
+
MAX_USER_PERMSET_ROWS = 500;
|
|
24903
|
+
DEFAULT_USER_PERMSET_ROWS = 200;
|
|
24904
|
+
USER_PERMSETS_BYTE_BUDGET = 36e3;
|
|
24905
|
+
USER_PERMSETS_DISCLOSURE = "Live = WHICH grantors this user holds right now (point-in-time as of queriedAt); vault sfi.effective_permissions = WHAT those grantors grant \u2014 pair them for a dual-provenance answer. The PROFILE grants permissions too: it is named here but its contents are not enumerated. Read-only.";
|
|
24906
|
+
liveUserPermsetsInputSchema = liveEnabledSchema.extend({
|
|
24907
|
+
/** Exact Username (preferred — unique) or exact User.Name. An ambiguous name
|
|
24908
|
+
* returns an honest candidate list, never a guess. */
|
|
24909
|
+
user: z2.string().min(1),
|
|
24910
|
+
/** Max assignment rows (default 200, hard cap 500); a ~36 KB byte budget may
|
|
24911
|
+
* trim further. `totalAssignments` is always the TRUE count. */
|
|
24912
|
+
limit: z2.number().int().min(1).max(MAX_USER_PERMSET_ROWS).optional(),
|
|
24913
|
+
orgAlias: z2.string().min(1).optional()
|
|
24914
|
+
});
|
|
24915
|
+
renderUserPermsetsMarkdown = (data) => {
|
|
24916
|
+
const direct = data.directPermsets.length;
|
|
24917
|
+
const via = data.viaGroups.reduce((n2, g2) => n2 + g2.permsets.length, 0);
|
|
24918
|
+
const lines = [
|
|
24919
|
+
`### Live permission-set holdings for ${data.user.name} (\`${data.user.username}\`)`,
|
|
24920
|
+
"",
|
|
24921
|
+
`Profile: **${data.user.profileName ?? "(none)"}**${data.user.isActive ? "" : " \u2014 user is INACTIVE"}.`,
|
|
24922
|
+
"",
|
|
24923
|
+
`**${data.totalAssignments.toLocaleString("en-US")}** assignment(s) (${direct} direct, ${via} via group(s) on this page)` + (data.expiredExcluded > 0 ? `; ${data.expiredExcluded} expired assignment(s) excluded` : "") + "."
|
|
24924
|
+
];
|
|
24925
|
+
const grantRows = (grants) => mdTable(["Permission set", "Label", "Expires"], grants.map((g2) => [g2.permissionSetName, g2.permissionSetLabel ?? "\u2014", g2.expirationDate ?? "\u2014"]));
|
|
24926
|
+
if (data.directPermsets.length > 0) {
|
|
24927
|
+
lines.push("", "**Direct permission sets**", "", grantRows(data.directPermsets));
|
|
24928
|
+
}
|
|
24929
|
+
for (const g2 of data.viaGroups) {
|
|
24930
|
+
lines.push("", `**Via permission set group \`${g2.groupName}\`**`, "", grantRows(g2.permsets));
|
|
24931
|
+
}
|
|
24932
|
+
if (data.capped) {
|
|
24933
|
+
lines.push("", `Showing ${data.returned} of ${data.totalAssignments} assignment(s).`);
|
|
24934
|
+
}
|
|
24935
|
+
lines.push("", data.disclosure, "", renderTrustFooter(data.trust));
|
|
24936
|
+
return lines.join("\n");
|
|
24937
|
+
};
|
|
24938
|
+
fitUserPermsets = (base, allRows) => {
|
|
24939
|
+
let slice = allRows;
|
|
24940
|
+
let byteTrimmed = false;
|
|
24941
|
+
for (; ; ) {
|
|
24942
|
+
const returned = slice.length;
|
|
24943
|
+
const capped = byteTrimmed || base.totalAssignments > returned;
|
|
24944
|
+
const direct = [];
|
|
24945
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
24946
|
+
for (const row of slice) {
|
|
24947
|
+
const { viaGroup, ...grant } = row;
|
|
24948
|
+
if (viaGroup === null) {
|
|
24949
|
+
direct.push(grant);
|
|
24950
|
+
} else {
|
|
24951
|
+
const list = byGroup.get(viaGroup) ?? [];
|
|
24952
|
+
list.push(grant);
|
|
24953
|
+
byGroup.set(viaGroup, list);
|
|
24954
|
+
}
|
|
24955
|
+
}
|
|
24956
|
+
const shape = {
|
|
24957
|
+
...base,
|
|
24958
|
+
returned,
|
|
24959
|
+
capped,
|
|
24960
|
+
directPermsets: direct,
|
|
24961
|
+
viaGroups: [...byGroup.entries()].map(([groupName, permsets]) => ({ groupName, permsets })),
|
|
24962
|
+
...byteTrimmed ? {
|
|
24963
|
+
note: `Assignment rows trimmed to ${returned} to stay within the response size limit; totalAssignments (${base.totalAssignments}) is the true count.`
|
|
24964
|
+
} : {}
|
|
24965
|
+
};
|
|
24966
|
+
const rendered = renderUserPermsetsMarkdown(shape);
|
|
24967
|
+
const bytes = Buffer.byteLength(JSON.stringify({ ...shape, rendered }), "utf8");
|
|
24968
|
+
if (bytes <= USER_PERMSETS_BYTE_BUDGET || slice.length <= 1) {
|
|
24969
|
+
return { ...shape, rendered };
|
|
24970
|
+
}
|
|
24971
|
+
slice = slice.slice(0, Math.max(1, Math.floor(slice.length * 0.85)));
|
|
24972
|
+
byteTrimmed = true;
|
|
24973
|
+
}
|
|
24974
|
+
};
|
|
24975
|
+
liveUserPermsetsHandler = async (ctx, input2, exec = nodeExecFile2) => {
|
|
24976
|
+
const gate = await gateLive(ctx, input2);
|
|
24977
|
+
if (!gate.ok)
|
|
24978
|
+
return gate;
|
|
24979
|
+
const org = gate.value;
|
|
24980
|
+
const queriedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24981
|
+
const limit = input2.limit ?? DEFAULT_USER_PERMSET_ROWS;
|
|
24982
|
+
const lit = soqlLiteral(input2.user);
|
|
24983
|
+
const nowLiteral = soqlDateTime(/* @__PURE__ */ new Date());
|
|
24984
|
+
const userSelect = `SELECT Id, Name, Username, IsActive, Profile.Name FROM User`;
|
|
24985
|
+
let userRow;
|
|
24986
|
+
const byUsername = await liveQuery(org, `${userSelect} WHERE Username = ${lit} LIMIT 2`, exec);
|
|
24987
|
+
if (!byUsername.available)
|
|
24988
|
+
return err(UNAVAILABLE_ERROR("User", org, byUsername.reason));
|
|
24989
|
+
if (byUsername.records.length === 1) {
|
|
24990
|
+
userRow = byUsername.records[0];
|
|
24991
|
+
} else {
|
|
24992
|
+
const byName = await liveQuery(org, `${userSelect} WHERE Name = ${lit} LIMIT 5`, exec);
|
|
24993
|
+
if (!byName.available)
|
|
24994
|
+
return err(UNAVAILABLE_ERROR("User", org, byName.reason));
|
|
24995
|
+
if (byName.records.length === 0) {
|
|
24996
|
+
return err({
|
|
24997
|
+
kind: "component-not-found",
|
|
24998
|
+
message: `No user with exact Username or Name '${input2.user}' in the live org. Usernames are unique \u2014 prefer the full Username.`
|
|
24999
|
+
});
|
|
25000
|
+
}
|
|
25001
|
+
if (byName.records.length > 1) {
|
|
25002
|
+
const candidates = byName.records.map((r2) => String(r2.Username ?? "")).filter((u2) => u2.length > 0).join(", ");
|
|
25003
|
+
return err({
|
|
25004
|
+
kind: "invalid-query",
|
|
25005
|
+
message: `'${input2.user}' matches ${byName.records.length} users in the live org \u2014 refusing to guess. Pass the exact Username. Candidates: ${candidates}.`
|
|
25006
|
+
});
|
|
25007
|
+
}
|
|
25008
|
+
userRow = byName.records[0];
|
|
25009
|
+
}
|
|
25010
|
+
const user = {
|
|
25011
|
+
id: String(userRow.Id ?? ""),
|
|
25012
|
+
name: String(userRow.Name ?? ""),
|
|
25013
|
+
username: String(userRow.Username ?? ""),
|
|
25014
|
+
isActive: userRow.IsActive === true,
|
|
25015
|
+
profileName: userRow.Profile?.Name ?? null
|
|
25016
|
+
};
|
|
25017
|
+
const scope = `AssigneeId = '${user.id}' AND PermissionSet.IsOwnedByProfile = false`;
|
|
25018
|
+
const notExpiredClause = ` AND (ExpirationDate = null OR ExpirationDate >= ${nowLiteral})`;
|
|
25019
|
+
const countQ = await liveQuery(org, `SELECT COUNT() FROM PermissionSetAssignment WHERE ${scope}${notExpiredClause}`, exec);
|
|
25020
|
+
if (!countQ.available) {
|
|
25021
|
+
return err(UNAVAILABLE_ERROR("PermissionSetAssignment", org, countQ.reason));
|
|
25022
|
+
}
|
|
25023
|
+
const totalAssignments = countQ.total;
|
|
25024
|
+
const expiredQ = await liveQuery(org, `SELECT COUNT() FROM PermissionSetAssignment WHERE ${scope} AND ExpirationDate < ${nowLiteral}`, exec);
|
|
25025
|
+
const expiredExcluded = expiredQ.available ? expiredQ.total : 0;
|
|
25026
|
+
const detailQ = await liveQuery(org, `SELECT Id, PermissionSet.Name, PermissionSet.Label, PermissionSetGroupId, PermissionSetGroup.DeveloperName, ExpirationDate FROM PermissionSetAssignment WHERE ${scope}${notExpiredClause} ORDER BY Id LIMIT ${limit}`, exec);
|
|
25027
|
+
if (!detailQ.available) {
|
|
25028
|
+
return err(UNAVAILABLE_ERROR("PermissionSetAssignment", org, detailQ.reason));
|
|
25029
|
+
}
|
|
25030
|
+
const rows = detailQ.records.map((r2) => ({
|
|
25031
|
+
assignmentId: String(r2.Id ?? ""),
|
|
25032
|
+
permissionSetName: String(r2.PermissionSet?.Name ?? ""),
|
|
25033
|
+
permissionSetLabel: r2.PermissionSet?.Label ?? null,
|
|
25034
|
+
expirationDate: r2.ExpirationDate ?? null,
|
|
25035
|
+
viaGroup: r2.PermissionSetGroupId ? r2.PermissionSetGroup?.DeveloperName ?? String(r2.PermissionSetGroupId) : null
|
|
25036
|
+
})).sort((a2, b2) => a2.permissionSetName.localeCompare(b2.permissionSetName));
|
|
25037
|
+
return ok({
|
|
25038
|
+
data: fitUserPermsets({
|
|
25039
|
+
user,
|
|
25040
|
+
totalAssignments,
|
|
25041
|
+
expiredExcluded,
|
|
25042
|
+
disclosure: USER_PERMSETS_DISCLOSURE,
|
|
25043
|
+
trust: liveTrust(queriedAt)
|
|
25044
|
+
}, rows),
|
|
25045
|
+
vaultState: livePlaneVaultState(ctx)
|
|
25046
|
+
});
|
|
25047
|
+
};
|
|
24187
25048
|
}
|
|
24188
25049
|
});
|
|
24189
25050
|
|
|
@@ -24806,6 +25667,73 @@ var init_intent_router = __esm({
|
|
|
24806
25667
|
/\bwhat_if_remove_picklist_value\b/
|
|
24807
25668
|
]
|
|
24808
25669
|
},
|
|
25670
|
+
// === R3 §5b — what-if CHANGE-TYPE WHITELIST honest routes ================
|
|
25671
|
+
// The what_if_* simulators cover a fixed change-type list (field type /
|
|
25672
|
+
// required / picklist value, flow deactivation, trigger disable, method
|
|
25673
|
+
// signature, profile merge/split). A what-if ask about a change type with
|
|
25674
|
+
// NO simulator (remove a record type, change a layout assignment, change a
|
|
25675
|
+
// flow variable's type) must not be absorbed by the nearest-neighbor
|
|
25676
|
+
// what_if_* — these rules catch those shapes FIRST and route the honest
|
|
25677
|
+
// dependency read (get_impact & friends) with an explicit no-simulator
|
|
25678
|
+
// disclosure, mirroring `permission-set-deactivation-impact`.
|
|
25679
|
+
{
|
|
25680
|
+
intent: "record-type-removal-impact",
|
|
25681
|
+
plane: "vault",
|
|
25682
|
+
tools: ["sfi.resolve", "sfi.get_impact", "sfi.recordtype_availability"],
|
|
25683
|
+
liveRequired: false,
|
|
25684
|
+
needsResolve: true,
|
|
25685
|
+
reason: "No what_if simulator exists for removing a record type, so this routes honestly to the dependency read: get_impact surfaces the flows, layouts, validation rules, and profiles referencing the record type, and recordtype_availability shows who can see it today \u2014 the truthful stand-in for a removal blast radius, not a fabricated simulation.",
|
|
25686
|
+
patterns: [
|
|
25687
|
+
/\b(?:remov\w+|delet\w+|dropp?\w*|consolidat\w+|retir\w+)\b[^.?!]{0,50}\brecord\s+types?\b/,
|
|
25688
|
+
/\brecord\s+types?\b[^.?!]{0,50}\b(?:is|are|was|were|gets?|being)\s+(?:removed|deleted|dropped|consolidated|retired)\b/
|
|
25689
|
+
]
|
|
25690
|
+
},
|
|
25691
|
+
{
|
|
25692
|
+
intent: "layout-assignment-change-impact",
|
|
25693
|
+
plane: "vault",
|
|
25694
|
+
tools: ["sfi.resolve", "sfi.layout_assignments", "sfi.get_impact"],
|
|
25695
|
+
liveRequired: false,
|
|
25696
|
+
needsResolve: true,
|
|
25697
|
+
reason: "No what_if simulator exists for changing a page-layout assignment, so this routes honestly to the current state: layout_assignments shows the Profile \xD7 RecordType assignments the change would rewire, plus get_impact for the dependency surface \u2014 a lookup plus impact read, not a fabricated simulation.",
|
|
25698
|
+
patterns: [
|
|
25699
|
+
/\b(?:chang\w+|swapp?\w*|reassign\w*|switch\w*)\b[^.?!]{0,60}\blayout\s+assignments?\b/,
|
|
25700
|
+
/\bwhat\s+(?:if|happens|breaks)\b[^.?!]{0,80}\blayout\s+assignments?\b/
|
|
25701
|
+
]
|
|
25702
|
+
},
|
|
25703
|
+
{
|
|
25704
|
+
intent: "flow-variable-type-change-impact",
|
|
25705
|
+
plane: "vault",
|
|
25706
|
+
tools: ["sfi.resolve", "sfi.get_impact", "sfi.explain_flow"],
|
|
25707
|
+
liveRequired: false,
|
|
25708
|
+
needsResolve: true,
|
|
25709
|
+
reason: "No what_if simulator models changing a flow variable's type (e.g. single record to collection), so this routes honestly to the callers: get_impact surfaces the flows/processes invoking it (whose passed values a type change breaks), and explain_flow the variable's uses inside \u2014 an interface-impact read, not a fabricated simulation.",
|
|
25710
|
+
patterns: [
|
|
25711
|
+
/\b(?:input|output)\s+variable\b[^.?!]{0,60}\btype\b/,
|
|
25712
|
+
/\bvariable\s+type\b[^.?!]{0,60}\bflow\b/,
|
|
25713
|
+
/\bflow\b[^.?!]{0,60}\bvariable\s+type\b/
|
|
25714
|
+
]
|
|
25715
|
+
},
|
|
25716
|
+
{
|
|
25717
|
+
// R3 §5b — ENUMERATION-vs-ID gate: `cpq_rule_chain` and
|
|
25718
|
+
// `cpq_quote_template_breakdown` REQUIRE a specific canonical id, so an
|
|
25719
|
+
// org-wide ask ("map the whole CPQ rule chain", "what quote template does
|
|
25720
|
+
// the org use?") must route the ENUMERATION path — the id-free dependency
|
|
25721
|
+
// map plus list_components — never commit an id-required tool without an
|
|
25722
|
+
// id. Hoisted ABOVE the call-graph/"map the chain" family and the generic
|
|
25723
|
+
// `cpq` rule (first-match): only org-wide CPQ phrasings land here; a
|
|
25724
|
+
// named rule/template still gets the per-id chain tools.
|
|
25725
|
+
intent: "cpq-enumeration",
|
|
25726
|
+
plane: "vault",
|
|
25727
|
+
tools: ["sfi.cpq_dependency_map", "sfi.list_components"],
|
|
25728
|
+
liveRequired: false,
|
|
25729
|
+
needsResolve: false,
|
|
25730
|
+
reason: "Org-wide CPQ enumeration: cpq_dependency_map maps the whole rule/template surface with no id needed, and list_components enumerates the CPQ rules/templates themselves. The per-id tools (cpq_rule_chain, cpq_quote_template_breakdown) need a specific named rule or template \u2014 pick one from the enumeration first.",
|
|
25731
|
+
patterns: [
|
|
25732
|
+
/\bmap\s+the\b[^.?!]{0,40}\bcpq\b|\bcpq\b[^.?!]{0,40}\brule\s+chain\b[^.?!]{0,60}\b(?:evaluation\s+order|all|every|org-?wide|whole)\b/,
|
|
25733
|
+
/\b(?:what|which)\s+(?:cpq\s+)?quote\s+templates?\b[^.?!]{0,50}\b(?:do(?:es)?\s+(?:we|the\s+org)|org)\s*use\b/,
|
|
25734
|
+
/\b(?:all|every|list)\b[^.?!]{0,30}\b(?:price|product|discount)\s+rules?\b[^.?!]{0,50}\b(?:evaluation\s+order|and\s+their)\b/
|
|
25735
|
+
]
|
|
25736
|
+
},
|
|
24809
25737
|
{
|
|
24810
25738
|
intent: "async-chain-depth",
|
|
24811
25739
|
plane: "vault",
|
|
@@ -25084,25 +26012,50 @@ var init_intent_router = __esm({
|
|
|
25084
26012
|
/\bhow\s+many\s+users?\b.*\bon\b.*\bprofiles?\b/
|
|
25085
26013
|
]
|
|
25086
26014
|
},
|
|
26015
|
+
{
|
|
26016
|
+
// ENGINE-ARC §2c (NEW arm): "zombie accounts" — active users with login
|
|
26017
|
+
// access but ZERO permission-set/PSG assignments. This is the User × PSA
|
|
26018
|
+
// anti-join (sfi.live_zombie_accounts), an explicit DELTA over
|
|
26019
|
+
// live_inactive_users: dormancy-only phrasings ("who hasn't logged in")
|
|
26020
|
+
// carry no permission-set-absence noun and fall through to inactive-users.
|
|
26021
|
+
// Every pattern requires either the literal "zombie" or a
|
|
26022
|
+
// no/zero/without + permission-set frame, so genuine holder-roster asks
|
|
26023
|
+
// ("which users have permission set X" — no negative) never land here.
|
|
26024
|
+
// Sits BEFORE profile-user-roster so "users with nothing assigned beyond
|
|
26025
|
+
// their profile" is not stolen by the profile-roster grammar.
|
|
26026
|
+
intent: "zombie-accounts",
|
|
26027
|
+
plane: "live",
|
|
26028
|
+
tools: ["sfi.live_zombie_accounts"],
|
|
26029
|
+
liveRequired: true,
|
|
26030
|
+
needsResolve: false,
|
|
26031
|
+
reason: 'Active users with NO permission-set/PSG assignments is a live User \xD7 PermissionSetAssignment anti-join (live_zombie_accounts). Note: a "zombie" still holds everything its PROFILE grants \u2014 the tool discloses that.',
|
|
26032
|
+
patterns: [
|
|
26033
|
+
/\bzombie\s+(?:accounts?|users?)\b/,
|
|
26034
|
+
// "which active users have no/zero permission sets (assigned)?" /
|
|
26035
|
+
// "users with zero permission set or group assignments" / "can log in
|
|
26036
|
+
// but have no permission set assignments"
|
|
26037
|
+
/\b(?:users?|accounts?)\b[^.?!]{0,50}\b(?:no|zero|without|not\s+any)\b[^.?!]{0,15}\bpermission\s+sets?\b/,
|
|
26038
|
+
// "which users have nothing assigned beyond their profile?"
|
|
26039
|
+
/\bnothing\s+assigned\s+beyond\b[^.?!]{0,25}\bprofiles?\b/,
|
|
26040
|
+
// "login access but no perm sets" (perm-set abbreviation variant)
|
|
26041
|
+
/\b(?:no|zero|without)\b[^.?!]{0,15}\bperm\s+sets?\b/
|
|
26042
|
+
]
|
|
26043
|
+
},
|
|
25087
26044
|
{
|
|
25088
26045
|
// "list everyone with the X profile" is a USER ROSTER ask: user-to-profile
|
|
25089
26046
|
// assignment is runtime User-record state, not vault metadata — the schema
|
|
25090
26047
|
// list rule used to claim it via "list ... profiles" and answer with the
|
|
25091
|
-
// Profile METADATA catalog (eval family D).
|
|
25092
|
-
//
|
|
25093
|
-
//
|
|
25094
|
-
//
|
|
26048
|
+
// Profile METADATA catalog (eval family D). ENGINE-ARC §4: the name-by-name
|
|
26049
|
+
// roster IS now built — sfi.live_permset_holders (kind:'profile') lists the
|
|
26050
|
+
// users live; live_group_count keeps the count side and live_inactive_users
|
|
26051
|
+
// the login-activity side. The old partial-answer gap block is DELETED.
|
|
25095
26052
|
intent: "profile-user-roster",
|
|
25096
26053
|
plane: "live",
|
|
25097
|
-
tools: ["sfi.live_group_count", "sfi.live_inactive_users"],
|
|
26054
|
+
tools: ["sfi.live_permset_holders", "sfi.live_group_count", "sfi.live_inactive_users"],
|
|
25098
26055
|
liveRequired: true,
|
|
25099
26056
|
needsResolve: false,
|
|
25100
|
-
reason: "Which users hold a profile is runtime User-record state:
|
|
25101
|
-
|
|
25102
|
-
category: "profile-user-roster",
|
|
25103
|
-
note: "Partial answer: live_group_count returns user COUNTS per profile and live_inactive_users lists dormant users, but a full name-by-name user roster per profile is not a built capability yet."
|
|
25104
|
-
},
|
|
25105
|
-
suggestArgs: () => ({ objectApiName: "User", groupByField: "ProfileId" }),
|
|
26057
|
+
reason: "Which users hold a profile is runtime User-record state: live_permset_holders (kind: profile) lists the roster name-by-name from the live org; live_group_count covers per-profile counts and live_inactive_users the login-activity side.",
|
|
26058
|
+
suggestArgs: () => ({ kind: "profile" }),
|
|
25106
26059
|
patterns: [
|
|
25107
26060
|
/\b(list|show|who\s+are)\b[^.?!]{0,20}\b(everyone|everybody|all\s+(the\s+)?users?|the\s+users?|people)\b[^.?!]{0,40}\b(with|on|assigned|holding|having)\b[^.?!]{0,40}\bprofile\b/,
|
|
25108
26061
|
/\b(which|what)\s+users?\b[^.?!]{0,40}\b(have|hold|are\s+on|with|assigned)\b[^.?!]{0,40}\bprofile\b/,
|
|
@@ -25326,6 +26279,41 @@ var init_intent_router = __esm({
|
|
|
25326
26279
|
/\bstandard\s+objects?\b.*\b(at\s+least\s+one\s+)?(apex\s+)?triggers?\b/
|
|
25327
26280
|
]
|
|
25328
26281
|
},
|
|
26282
|
+
{
|
|
26283
|
+
// ENGINE-ARC §4 (NEW arm): runtime queue / public-group MEMBERSHIP asks
|
|
26284
|
+
// ("who's in the Support queue", "members of the X public group", "can the
|
|
26285
|
+
// X queue own Case"). GroupMember rows are runtime state the vault does not
|
|
26286
|
+
// model — sfi.live_group_members reads them live (polymorphic user/group/
|
|
26287
|
+
// role split, QueueSobject supported objects, vault-drift cross-check).
|
|
26288
|
+
// Sits BEFORE group-count, which used to claim "who's in the X queue" with
|
|
26289
|
+
// a generic GROUP BY count; the roster tool now owns that vocabulary.
|
|
26290
|
+
// Boundary notes: "which queues are EMPTY" (declared-metadata hygiene
|
|
26291
|
+
// sweep) stays on empty-queues-groups; the neutral "which queues does
|
|
26292
|
+
// X_Queue route to and who are the members" stays on queue-membership
|
|
26293
|
+
// (vault get_component — declared members); a PSG "member of" ask never
|
|
26294
|
+
// lands here (patterns require the literal queue / public group noun).
|
|
26295
|
+
intent: "queue-group-member-roster",
|
|
26296
|
+
plane: "live",
|
|
26297
|
+
tools: ["sfi.live_group_members"],
|
|
26298
|
+
liveRequired: true,
|
|
26299
|
+
needsResolve: false,
|
|
26300
|
+
reason: "Who is actually IN a queue or public group is runtime GroupMember data \u2014 live_group_members lists the current roster (users, nested groups, roles) from the live org and cross-checks the vault-declared member count.",
|
|
26301
|
+
patterns: [
|
|
26302
|
+
// "who's / who is / who are ... in|on|member of ... the X queue|public group"
|
|
26303
|
+
/\bwho(?:'?s|\s+is|\s+are)?\b[^.?!]{0,30}\b(?:in|on|member\s+of)\b[^.?!]{0,40}\b(?:queues?|public\s+groups?)\b/,
|
|
26304
|
+
// "which users are in the Support queue?"
|
|
26305
|
+
/\b(?:which|what)\s+users?\b[^.?!]{0,30}\b(?:in|on|belong\s+to)\b[^.?!]{0,40}\b(?:queues?|public\s+groups?)\b/,
|
|
26306
|
+
// "members of the X queue" / "list the members of the Y public group"
|
|
26307
|
+
/\bmembers?\s+of\b[^.?!]{0,40}\b(?:queues?|public\s+groups?)\b/,
|
|
26308
|
+
// "the current membership of the X queue"
|
|
26309
|
+
/\bmembership\s+of\b[^.?!]{0,40}\b(?:queues?|public\s+groups?)\b/,
|
|
26310
|
+
// q267: "can the ADA_Team_Queue own Case records?" / "what objects does
|
|
26311
|
+
// the X queue support?" — QueueSobject supported objects.
|
|
26312
|
+
/\bcan\s+(?:the\s+)?\S+\s*queue\s+own\b/,
|
|
26313
|
+
/\bqueues?\b[^.?!]{0,30}\b(?:own|support)\b[^.?!]{0,30}\b(?:case|lead|objects?|records?)\b/,
|
|
26314
|
+
/\bobjects?\s+(?:does|can)\b[^.?!]{0,40}\bqueue\s+(?:support|own)\b/
|
|
26315
|
+
]
|
|
26316
|
+
},
|
|
25329
26317
|
{
|
|
25330
26318
|
intent: "group-count",
|
|
25331
26319
|
plane: "live",
|
|
@@ -25345,10 +26333,10 @@ var init_intent_router = __esm({
|
|
|
25345
26333
|
// "how many".
|
|
25346
26334
|
/\bcount\b[^.?!]{0,60}\bgrouped?\s+by\b/,
|
|
25347
26335
|
/\bcount\s+of\b[^.?!]{0,60}\bby\b/,
|
|
25348
|
-
/\bcount\b[^.?!]{0,60}\brecords?\s+by\b
|
|
25349
|
-
// "who's in the ADA Team Queue"
|
|
25350
|
-
//
|
|
25351
|
-
|
|
26336
|
+
/\bcount\b[^.?!]{0,60}\brecords?\s+by\b/
|
|
26337
|
+
// NOTE (ENGINE-ARC §4): "who's in the ADA Team Queue" used to land here
|
|
26338
|
+
// as a generic GROUP BY count; the queue-group-member-roster arm ABOVE
|
|
26339
|
+
// now owns that vocabulary with the dedicated live_group_members roster.
|
|
25352
26340
|
]
|
|
25353
26341
|
},
|
|
25354
26342
|
{
|
|
@@ -25825,6 +26813,58 @@ var init_intent_router = __esm({
|
|
|
25825
26813
|
/\brecord\s?type\s?id\b[^.?!]{0,60}\b(?:pick|select|choose|create|use)\b/
|
|
25826
26814
|
]
|
|
25827
26815
|
},
|
|
26816
|
+
{
|
|
26817
|
+
// ENGINE-ARC §4 (NEW arm): the REVERSE user→grantors direction — "what
|
|
26818
|
+
// permission sets does USER Jane hold". A different contract from the
|
|
26819
|
+
// holder-roster direction (permset-user-roster below) and from the vault
|
|
26820
|
+
// GRANTS direction: PermissionSetAssignment rows for one assignee are
|
|
26821
|
+
// runtime state, so sfi.live_user_permsets reads them live (direct sets vs
|
|
26822
|
+
// via-PSG, expirations, profile named). The ordered pair with vault
|
|
26823
|
+
// sfi.effective_permissions is deliberate DUAL PROVENANCE: live = WHICH
|
|
26824
|
+
// grantors she holds; vault = WHAT those grantors grant. Sits BEFORE
|
|
26825
|
+
// effective-permissions, which used to first-match these phrasings and
|
|
26826
|
+
// could only describe grants, never the user's actual assignments.
|
|
26827
|
+
intent: "user-permset-holdings",
|
|
26828
|
+
plane: "live",
|
|
26829
|
+
tools: ["sfi.live_user_permsets", "sfi.effective_permissions"],
|
|
26830
|
+
liveRequired: true,
|
|
26831
|
+
needsResolve: false,
|
|
26832
|
+
reason: "What a named USER holds (their PermissionSetAssignment rows) is runtime state: live_user_permsets lists the direct sets and via-PSG assignments from the live org; effective_permissions then expands what those grantors grant (dual provenance).",
|
|
26833
|
+
patterns: [
|
|
26834
|
+
// Permission sets assigned to a named user (moved here from
|
|
26835
|
+
// effective-permissions — the router used to first-match these to the
|
|
26836
|
+
// vault GRANTS tool, which cannot know a user's assignments).
|
|
26837
|
+
/\bpermission\s+sets?\b.*\bassigned\b.*\buser\b/,
|
|
26838
|
+
/\bwhat\s+permission\s+sets?\b.*\bassigned\b/,
|
|
26839
|
+
// "what permission sets does Jane Doe have / hold" — requires the
|
|
26840
|
+
// PERMISSION SET(S) noun phrase, so "what permissions does the X profile
|
|
26841
|
+
// have" (grants direction) stays on effective-permissions. The
|
|
26842
|
+
// `(?!\s+group)` lookahead keeps "which permission set GROUPS are
|
|
26843
|
+
// assigned to nobody" on the unassigned-permset-groups hygiene arm.
|
|
26844
|
+
/\b(?:what|which)\s+(?:permission\s+sets?|perm\s+sets?)(?!\s+group)\b[^.?!]{0,20}\b(?:does|do|is|are)\b[^.?!]{0,50}\b(?:have|hold|holds|assigned|carry)\b/,
|
|
26845
|
+
// "which permission set groups is <UserName> a member of?" — anchored on
|
|
26846
|
+
// the literal "member of" so "which PSGs are assigned to NOBODY" (the
|
|
26847
|
+
// unassigned-permset-groups hygiene ask, a later rule) is never stolen.
|
|
26848
|
+
// [^?!] (not [^.?!]): usernames are email-shaped and contain dots.
|
|
26849
|
+
/\bwhich\s+permission\s+set\s+groups?\s+(?:is|are)\b[^?!]{0,60}\bmember\s+of\b/,
|
|
26850
|
+
// "which permission sets is jane.doe@example.com assigned right now?" —
|
|
26851
|
+
// the grantee token sits between is/are and "assigned"; \S+ spans the
|
|
26852
|
+
// email-shaped username that clause-bounded classes cannot.
|
|
26853
|
+
/\bpermission\s+sets?\s+(?:is|are)\s+\S+(?:\s+\S+)?\s+assigned\b/,
|
|
26854
|
+
// "what does the user Jane hold — direct sets and via groups?"
|
|
26855
|
+
/\bwhat\s+does\s+(?:the\s+)?user\b[^.?!]{0,40}\bhold\b/,
|
|
26856
|
+
// "show me every permission set assigned to <UserName>" — the grantee is
|
|
26857
|
+
// named after "assigned to" (holder-direction phrasings name the SET
|
|
26858
|
+
// after the users noun and never match this). The lookahead excludes
|
|
26859
|
+
// "assigned to NOBODY / no one / anyone" — that is the vault
|
|
26860
|
+
// unassigned-permsets hygiene sweep, not a user's holdings.
|
|
26861
|
+
/\b(?:every|all|each)\s+permission\s+sets?\s+assigned\s+to\s+(?!nobody\b|no\s+one\b|noone\b|anyone\b|anybody\b)/,
|
|
26862
|
+
// "list <UserName>'s permission set and PSG assignments"
|
|
26863
|
+
/\S+'s\s+permission\s+sets?\b/,
|
|
26864
|
+
// "does <UserName> have any expiring permission set assignments?"
|
|
26865
|
+
/\bexpir\w+\s+permission\s+set\s+assignments?\b/
|
|
26866
|
+
]
|
|
26867
|
+
},
|
|
25828
26868
|
{
|
|
25829
26869
|
intent: "effective-permissions",
|
|
25830
26870
|
plane: "vault",
|
|
@@ -25838,9 +26878,10 @@ var init_intent_router = __esm({
|
|
|
25838
26878
|
// "what permissions does the Sales User profile have" — a top
|
|
25839
26879
|
// baseline-300 unrouted cluster (P14-ROUTER-goldset-expand).
|
|
25840
26880
|
/\bwhat\s+permissions?\s+(does|do)\b.*\b(profiles?|permission\s+sets?|users?)\b/,
|
|
25841
|
-
//
|
|
25842
|
-
|
|
25843
|
-
|
|
26881
|
+
// NOTE (ENGINE-ARC §4): the "permission sets assigned to a user"
|
|
26882
|
+
// phrasings moved to the user-permset-holdings arm ABOVE — a user's
|
|
26883
|
+
// actual assignments are runtime state (live_user_permsets); this vault
|
|
26884
|
+
// arm keeps the GRANTS direction only.
|
|
25844
26885
|
// REACH (permissions/access cluster): "does/can the <Named> profile /
|
|
25845
26886
|
// <Named> perm set have|give|grant|read|edit|create|delete|access|see|
|
|
25846
26887
|
// change <object>". These name a SPECIFIC granter (a profile/permission
|
|
@@ -26111,6 +27152,35 @@ var init_intent_router = __esm({
|
|
|
26111
27152
|
/\bhighlights?\b.*\b(vs|versus|full)\b.*\blayout\b/
|
|
26112
27153
|
]
|
|
26113
27154
|
},
|
|
27155
|
+
{
|
|
27156
|
+
// COMPONENT-TYPE CONFUSION (router-v2 R2, type-confusion trap family):
|
|
27157
|
+
// "is <Name> a flow or a trigger?", "is that an Apex class or a flow?",
|
|
27158
|
+
// "the name has 'Trigger' in it but is it actually a test class?", "what
|
|
27159
|
+
// type is that?". The user is unsure WHAT KIND of component a name is —
|
|
27160
|
+
// resolve answers the type question, and the two explainers cover
|
|
27161
|
+
// whichever family it lands on (the stage-5 type guard swaps the
|
|
27162
|
+
// incompatible one once the entity resolves). MUST precede
|
|
27163
|
+
// automation-on-object, whose "triggers … on <object>" pattern otherwise
|
|
27164
|
+
// steals "…an actual trigger on Contact?".
|
|
27165
|
+
intent: "component-type",
|
|
27166
|
+
plane: "vault",
|
|
27167
|
+
tools: ["sfi.resolve", "sfi.explain_flow", "sfi.explain_apex_method"],
|
|
27168
|
+
liveRequired: false,
|
|
27169
|
+
needsResolve: true,
|
|
27170
|
+
reason: "The question asks WHICH KIND of component a name is (flow vs trigger vs Apex class vs test class). Resolve the name to its actual type first; then explain it with the family-appropriate tool \u2014 the resolver output, not the name, decides which.",
|
|
27171
|
+
patterns: [
|
|
27172
|
+
// Two component-family nouns joined by "or" inside one clause:
|
|
27173
|
+
// "is <name>/that/it (actually) a flow … or (an actual) trigger".
|
|
27174
|
+
/\bis\s+(?:that|this|it|[a-z0-9_]+)\s+(?:actually\s+)?(?:an?\s+)?(?:apex(?:\s+(?:class|test\s+class|logging))?|flow|trigger|test\s+class)\b[^.?!]{0,40}\bor\s+(?:an?\s+)?(?:actual(?:ly)?\s+a?\s*)?(?:apex(?:\s+class)?|flow|trigger|test\s+class|something\s+else)/,
|
|
27175
|
+
// Name-vs-nature: "…but is it actually a test class?"
|
|
27176
|
+
/\bbut\s+is\s+(?:it|that)\s+(?:actually\s+)?an?\s+(?:test\s+class|trigger|flow|apex\s+class)\b/,
|
|
27177
|
+
// "what type is that / what type of component is <name>" — `that/this`
|
|
27178
|
+
// only (not `it`), and never when the question names a __c FIELD:
|
|
27179
|
+
// "what type is it?" about CON_...__c asks the field's DATA type, not
|
|
27180
|
+
// the component family (the cov3-029 negative).
|
|
27181
|
+
/^(?!.*__c\b)(?=[\s\S]*\bwhat\s+type\s+(?:is\s+(?:that|this)|of\s+component\s+is)\b)/
|
|
27182
|
+
]
|
|
27183
|
+
},
|
|
26114
27184
|
{
|
|
26115
27185
|
intent: "field-access",
|
|
26116
27186
|
plane: "vault",
|
|
@@ -26314,60 +27384,105 @@ var init_intent_router = __esm({
|
|
|
26314
27384
|
]
|
|
26315
27385
|
},
|
|
26316
27386
|
{
|
|
26317
|
-
// HONEST GAP
|
|
26318
|
-
//
|
|
26319
|
-
//
|
|
26320
|
-
//
|
|
26321
|
-
//
|
|
26322
|
-
//
|
|
27387
|
+
// PARTIAL FLIP (ENGINE-ARC §4, was HONEST GAP P0b): PermissionSetGroup
|
|
27388
|
+
// ASSIGNMENT is now answerable live per group — sfi.live_permset_holders
|
|
27389
|
+
// (kind:'permissionSetGroup') returns the true holder count and roster for
|
|
27390
|
+
// a NAMED PSG, so "is PSG X assigned to nobody" is a live zero-holder
|
|
27391
|
+
// check. What is still unbuilt is an enumerate-ALL mode (vault PSG list
|
|
27392
|
+
// minus a live GROUP BY PermissionSetGroupId sweep in one call) — the gap
|
|
27393
|
+
// note is DOWNGRADED to that partial until Deferred-2 ships. Must sit
|
|
27394
|
+
// AFTER unassigned-permsets: the lookahead above already refuses
|
|
27395
|
+
// "permission set groups", so this rule catches it on the fall-through.
|
|
26323
27396
|
intent: "unassigned-permset-groups",
|
|
26324
|
-
plane: "
|
|
26325
|
-
tools: [],
|
|
26326
|
-
liveRequired:
|
|
27397
|
+
plane: "live",
|
|
27398
|
+
tools: ["sfi.live_permset_holders"],
|
|
27399
|
+
liveRequired: true,
|
|
26327
27400
|
needsResolve: false,
|
|
26328
|
-
reason: "
|
|
27401
|
+
reason: "Whether a permission set group is assigned to anyone is runtime PermissionSetAssignment state: live_permset_holders (kind: permissionSetGroup) returns the true holder count per named PSG.",
|
|
26329
27402
|
gap: {
|
|
26330
27403
|
category: "unassigned-permset-groups",
|
|
26331
|
-
note: "
|
|
27404
|
+
note: "Partial: live_permset_holders checks ONE named permission set group at a time (a zero effectiveTotal = unassigned). A single-call sweep of ALL PSGs is not built yet \u2014 enumerate the vault PSG list first, then check candidates live. Do not substitute unassigned_permission_sets, which covers PermissionSet only."
|
|
26332
27405
|
},
|
|
27406
|
+
suggestArgs: () => ({ kind: "permissionSetGroup" }),
|
|
26333
27407
|
patterns: [
|
|
26334
27408
|
/\b(unassigned|unused|empty)\b[^.?!]{0,40}\bpermission\s+set\s+groups?\b/,
|
|
26335
27409
|
/\bpermission\s+set\s+groups?\b[^.?!]{0,40}\b(no\s+one|nobody|unassigned|unused|assigned\s+to\s+nobody)\b/
|
|
26336
27410
|
]
|
|
26337
27411
|
},
|
|
26338
27412
|
{
|
|
26339
|
-
// HONEST GAP
|
|
26340
|
-
// holder ROSTER — PermissionSetAssignment is
|
|
26341
|
-
// vault does not model, and
|
|
26342
|
-
//
|
|
26343
|
-
//
|
|
26344
|
-
//
|
|
26345
|
-
//
|
|
26346
|
-
//
|
|
27413
|
+
// FULL FLIP (ENGINE-ARC §4, was HONEST GAP eval family D): "which USERS
|
|
27414
|
+
// have permission set X" is a holder ROSTER — PermissionSetAssignment is
|
|
27415
|
+
// runtime assignment data the vault does not model, and it is now answered
|
|
27416
|
+
// LIVE by sfi.live_permset_holders (kinds permissionSet | permissionSetGroup
|
|
27417
|
+
// | profile | auto; PSG-trap-aware: direct holders vs via-group holders,
|
|
27418
|
+
// deduped effectiveTotal). The gap block is DELETED. The
|
|
27419
|
+
// do-not-substitute warning stays in `reason`: effective_permissions /
|
|
27420
|
+
// object_access_audit describe what a permission set GRANTS, not who HOLDS
|
|
27421
|
+
// it. The reverse direction ("what permission sets does user X have") is
|
|
27422
|
+
// the user-permset-holdings arm (live_user_permsets).
|
|
26347
27423
|
intent: "permset-user-roster",
|
|
26348
|
-
plane: "
|
|
26349
|
-
tools: [],
|
|
26350
|
-
liveRequired:
|
|
27424
|
+
plane: "live",
|
|
27425
|
+
tools: ["sfi.live_permset_holders"],
|
|
27426
|
+
liveRequired: true,
|
|
27427
|
+
needsResolve: false,
|
|
27428
|
+
reason: "Which users hold a permission set is runtime PermissionSetAssignment state: live_permset_holders lists the roster live, including holders via permission set groups. Do not substitute effective_permissions or object_access_audit \u2014 they describe what a permission set GRANTS, not who holds it.",
|
|
27429
|
+
patterns: [
|
|
27430
|
+
// "perm set(s)" / "permset(s)" ride along with the full "permission
|
|
27431
|
+
// set(s)" spelling — q1213 ("who has the View_Fraud_Score_Component
|
|
27432
|
+
// perm set", the design doc's grounding miss) uses the abbreviation,
|
|
27433
|
+
// and the user-permset-holdings arm above already accepts it.
|
|
27434
|
+
/\b(which|what|list)\s+users?\b[^.?!]{0,40}\b(have|hold|with|assigned)\b[^.?!]{0,40}\b(?:permission\s+sets?|perm\s+sets?|permsets?)\b/,
|
|
27435
|
+
/\bwho\s+(has|holds|is\s+assigned)\b[^.?!]{0,50}\b(?:permission\s+sets?|perm\s+sets?|permsets?)\b/,
|
|
27436
|
+
/\b(everyone|everybody|all\s+users?)\b[^.?!]{0,30}\bwith\b[^.?!]{0,40}\b(?:permission\s+sets?|perm\s+sets?|permsets?)\b/,
|
|
27437
|
+
/\busers?\b[^.?!]{0,30}\bassigned\b[^.?!]{0,30}\b(?:permission\s+sets?|perm\s+sets?|permsets?)\b/
|
|
27438
|
+
]
|
|
27439
|
+
},
|
|
27440
|
+
{
|
|
27441
|
+
// PARTIAL FLIP (ENGINE-ARC §4, was HONEST GAP round-2 q1948/q1559):
|
|
27442
|
+
// "which PSG grants the X custom permission / the Y role" is a
|
|
27443
|
+
// PermissionSetGroup COMPOSITION lookup. live_permset_holders now surfaces
|
|
27444
|
+
// PSG composition live via PermissionSetGroupComponent (probing a
|
|
27445
|
+
// permission SET reports which PSGs contain it), so the roster/containment
|
|
27446
|
+
// half is answerable. The 2-hop chain "which PSG grants custom permission
|
|
27447
|
+
// X" (custom permission → permission set → PSG, q1916) remains an honest
|
|
27448
|
+
// gap — the slimmed note below keeps it — and roles are not granted by
|
|
27449
|
+
// PSGs at all, so substituting object_access_audit / effective_permissions
|
|
27450
|
+
// would still be confidently wrong. Requires the GROUP noun: "which
|
|
27451
|
+
// permission SETS grant edit on X" (no `group`) stays on the real
|
|
27452
|
+
// field/effective-permissions routes.
|
|
27453
|
+
intent: "permset-group-grants",
|
|
27454
|
+
plane: "live",
|
|
27455
|
+
tools: ["sfi.live_permset_holders"],
|
|
27456
|
+
liveRequired: true,
|
|
26351
27457
|
needsResolve: false,
|
|
26352
|
-
reason: "
|
|
27458
|
+
reason: "PSG composition is runtime state: live_permset_holders reads PermissionSetGroupComponent live (probe the permission SET to see which PSGs contain it). Roles are never granted by PSGs.",
|
|
26353
27459
|
gap: {
|
|
26354
|
-
category: "permset-
|
|
26355
|
-
note: '
|
|
27460
|
+
category: "permset-group-grants",
|
|
27461
|
+
note: 'Partial: live_permset_holders surfaces which PSGs CONTAIN a given permission set (PermissionSetGroupComponent). The 2-hop chain "which PSG grants custom permission X" (custom permission \u2192 permission set \u2192 PSG) is still unbuilt \u2014 resolve the custom permission to its granting permission sets in the vault first, then probe each set live. Do not substitute effective_permissions or object_access_audit for the PSG-composition step.'
|
|
26356
27462
|
},
|
|
26357
27463
|
patterns: [
|
|
26358
|
-
|
|
26359
|
-
|
|
26360
|
-
|
|
26361
|
-
|
|
27464
|
+
// Tempered verb→grants gap: "which permission set groups REFERENCE a
|
|
27465
|
+
// permission set that grants X" is a graph-edge read (answerable) — a
|
|
27466
|
+
// reference/contain/include verb between the PSG noun and `grants`
|
|
27467
|
+
// breaks the match, so only the PSG-as-grantor ask gaps. `not` breaks it
|
|
27468
|
+
// too: "why would a PSG NOT grant expected access" is a troubleshooting
|
|
27469
|
+
// ask that stays on its pre-round-2 route (sweep-parity pin).
|
|
27470
|
+
/\b(?:which|what)\s+(?:permission\s+set\s+groups?|psgs?)\b(?:(?!\b(?:references?|referencing|contains?|containing|includes?|including|not)\b)[^.?!]){0,60}\bgrants?\b/,
|
|
27471
|
+
/\b(?:permission\s+set\s+groups?|psgs?)\b(?:(?!\b(?:references?|referencing|contains?|containing|includes?|including|not)\b)[^.?!]){0,40}\bgrants?\b[^.?!]{0,60}\b(?:custom\s+permission|permission|role|access)\b/
|
|
26362
27472
|
]
|
|
26363
27473
|
},
|
|
26364
27474
|
{
|
|
26365
27475
|
intent: "empty-queues-groups",
|
|
26366
|
-
|
|
26367
|
-
|
|
27476
|
+
// ENGINE-ARC §4: vault scan stays PRIMARY; sfi.live_group_members is
|
|
27477
|
+
// appended as the optional runtime-verification secondary (declared
|
|
27478
|
+
// metadata can drift from runtime GroupMember rows — the live tool
|
|
27479
|
+
// measures that drift per queue/group). Hybrid like unused-fields:
|
|
27480
|
+
// liveRequired stays false, the vault half answers alone.
|
|
27481
|
+
plane: "hybrid",
|
|
27482
|
+
tools: ["sfi.empty_queues_and_groups", "sfi.live_group_members"],
|
|
26368
27483
|
liveRequired: false,
|
|
26369
27484
|
needsResolve: false,
|
|
26370
|
-
reason: "Queues / public groups with no members \u2014 vault membership scan.",
|
|
27485
|
+
reason: "Queues / public groups with no members \u2014 vault membership scan first; live_group_members optionally verifies a candidate against runtime GroupMember rows (declared metadata can drift).",
|
|
26371
27486
|
patterns: [
|
|
26372
27487
|
/\b(empty|unused)\b.*\b(queues?|groups?)\b/,
|
|
26373
27488
|
/\b(queues?|public\s+groups?)\b.*\b(empty|no\s+members?|unused)\b/,
|
|
@@ -32080,6 +33195,10 @@ var init_capabilities = __esm({
|
|
|
32080
33195
|
"sfi.live_org_limits",
|
|
32081
33196
|
"sfi.live_inactive_users",
|
|
32082
33197
|
"sfi.live_license_usage",
|
|
33198
|
+
"sfi.live_permset_holders",
|
|
33199
|
+
"sfi.live_zombie_accounts",
|
|
33200
|
+
"sfi.live_group_members",
|
|
33201
|
+
"sfi.live_user_permsets",
|
|
32083
33202
|
"sfi.live_report_usage",
|
|
32084
33203
|
"sfi.live_folder_access",
|
|
32085
33204
|
"sfi.live_email_template_usage",
|
|
@@ -32180,6 +33299,10 @@ var init_capabilities = __esm({
|
|
|
32180
33299
|
"sfi.live_org_limits",
|
|
32181
33300
|
"sfi.live_inactive_users",
|
|
32182
33301
|
"sfi.live_license_usage",
|
|
33302
|
+
"sfi.live_permset_holders",
|
|
33303
|
+
"sfi.live_zombie_accounts",
|
|
33304
|
+
"sfi.live_group_members",
|
|
33305
|
+
"sfi.live_user_permsets",
|
|
32183
33306
|
"sfi.live_report_usage",
|
|
32184
33307
|
"sfi.live_folder_access",
|
|
32185
33308
|
"sfi.live_email_template_usage",
|
|
@@ -34219,19 +35342,72 @@ var init_component_history = __esm({
|
|
|
34219
35342
|
}
|
|
34220
35343
|
});
|
|
34221
35344
|
|
|
35345
|
+
// ../mcp/dist/src/tools/vault-assignment-disclosure.js
|
|
35346
|
+
var USER_ASSIGNMENT_NOT_IN_VAULT, PERMSET_INTERSECTION_NOT_AVAILABLE, SHARING_USER_ENUMERATION_NOT_AVAILABLE, ASSIGNMENT_DATA_LIVE_TOOLS, userAssignmentUnavailable;
|
|
35347
|
+
var init_vault_assignment_disclosure = __esm({
|
|
35348
|
+
"../mcp/dist/src/tools/vault-assignment-disclosure.js"() {
|
|
35349
|
+
"use strict";
|
|
35350
|
+
init_src3();
|
|
35351
|
+
USER_ASSIGNMENT_NOT_IN_VAULT = "User and PermissionSetAssignment data are not retrieved into this vault \u2014 which users hold a profile, permission set, or group membership cannot be answered offline. It is answerable via the live plane: run `sfi.live_permset_holders` (who holds a permission set / PSG / profile), `sfi.live_group_members` (who is in a queue / public group), or `sfi.live_user_permsets` (what a named user holds) \u2014 read-only, consent-gated \u2014 or refresh with a PermissionSetAssignment facts capture for offline holder counts.";
|
|
35352
|
+
PERMSET_INTERSECTION_NOT_AVAILABLE = "Which users have multiple permission sets assigned requires PermissionSetAssignment rows (user assignment data not in vault). This tool can list each permission set structurally but cannot name users with both assignments offline. It is answerable via the live plane: `sfi.live_permset_holders` (read-only, consent-gated) lists the holders of each set for the intersection.";
|
|
35353
|
+
SHARING_USER_ENUMERATION_NOT_AVAILABLE = "Sharing rules name group/role targets, but User records and group membership to individual users are not fully available offline \u2014 cannot enumerate every user who effectively receives Edit from a group-based sharing rule without live User / GroupMember / PermissionSetAssignment data. It is answerable via the live plane: `sfi.live_group_members` (read-only, consent-gated) enumerates the current members of a named queue / public group.";
|
|
35354
|
+
ASSIGNMENT_DATA_LIVE_TOOLS = [
|
|
35355
|
+
"sfi.live_permset_holders",
|
|
35356
|
+
"sfi.live_user_permsets",
|
|
35357
|
+
"sfi.live_group_members",
|
|
35358
|
+
"sfi.live_zombie_accounts"
|
|
35359
|
+
];
|
|
35360
|
+
userAssignmentUnavailable = (ctx) => {
|
|
35361
|
+
const userCov = summarizeCoverage(ctx.manifest, ["User"]);
|
|
35362
|
+
const psaCov = summarizeCoverage(ctx.manifest, ["PermissionSetAssignment"]);
|
|
35363
|
+
return userCov.status !== "complete" || psaCov.status !== "complete";
|
|
35364
|
+
};
|
|
35365
|
+
}
|
|
35366
|
+
});
|
|
35367
|
+
|
|
34222
35368
|
// ../mcp/dist/src/tools/coverage-report.js
|
|
34223
35369
|
import { z as z25 } from "zod";
|
|
34224
|
-
var TOP_UNCOVERED_FAMILIES_CAP, COVERAGE_DISCLOSURE, coverageReportInputSchema, partitionCoverage, coverageReportHandler;
|
|
35370
|
+
var TOP_UNCOVERED_FAMILIES_CAP, COVERAGE_DISCLOSURE, coverageReportInputSchema, ASSIGNMENT_DATA_NOT_A_GAP_REASON, buildAssignmentDataCoverage, partitionCoverage, coverageReportHandler;
|
|
34225
35371
|
var init_coverage_report = __esm({
|
|
34226
35372
|
"../mcp/dist/src/tools/coverage-report.js"() {
|
|
34227
35373
|
"use strict";
|
|
34228
35374
|
init_src();
|
|
35375
|
+
init_src2();
|
|
34229
35376
|
init_src3();
|
|
35377
|
+
init_live_plane();
|
|
35378
|
+
init_vault_assignment_disclosure();
|
|
34230
35379
|
TOP_UNCOVERED_FAMILIES_CAP = 10;
|
|
34231
35380
|
COVERAGE_DISCLOSURE = "Coverage describes what the last `sf project retrieve` requested and returned \u2014 not what exists in the org. A type listed under `notModeled` is not analyzed by this product at all; its absence from any result means 'not checked', never 'none'. Re-run `/sfi-refresh` after widening your retrieve manifest to close a gap. `topUncoveredFamilies` ranks (by skipped-file volume) directories that WERE retrieved but not modeled by an extractor \u2014 a listed family is retrieved-but-not-modeled, never 'absent'.";
|
|
34232
35381
|
coverageReportInputSchema = z25.object({
|
|
34233
35382
|
type: z25.string().min(1).optional()
|
|
34234
35383
|
});
|
|
35384
|
+
ASSIGNMENT_DATA_NOT_A_GAP_REASON = "runtime data object \u2014 by design, not a retrieve gap";
|
|
35385
|
+
buildAssignmentDataCoverage = async (ctx) => {
|
|
35386
|
+
let present = false;
|
|
35387
|
+
let capturedAt = null;
|
|
35388
|
+
try {
|
|
35389
|
+
const rows = await readFacts(ctx.graph, {
|
|
35390
|
+
subjectId: ACTIVE_HOLDERS_COMPLETE_SUBJECT,
|
|
35391
|
+
metric: "activeHolders",
|
|
35392
|
+
limit: 1
|
|
35393
|
+
});
|
|
35394
|
+
const marker = rows.ok ? rows.value[0] : void 0;
|
|
35395
|
+
if (marker !== void 0 && typeof marker.value === "object" && marker.value !== null && marker.value.complete === true) {
|
|
35396
|
+
present = true;
|
|
35397
|
+
capturedAt = marker.capturedAt;
|
|
35398
|
+
}
|
|
35399
|
+
} catch {
|
|
35400
|
+
}
|
|
35401
|
+
const liveConsent = (await resolveLiveAccess(ctx.manifest.sourceOrg)).allowed;
|
|
35402
|
+
return {
|
|
35403
|
+
vaultModeled: false,
|
|
35404
|
+
reason: ASSIGNMENT_DATA_NOT_A_GAP_REASON,
|
|
35405
|
+
liveTools: ASSIGNMENT_DATA_LIVE_TOOLS,
|
|
35406
|
+
liveConsent,
|
|
35407
|
+
factsCounts: { present, capturedAt },
|
|
35408
|
+
rendered: `Runtime assignment data: not in vault (by design). Live: ${liveConsent ? "available" : "consent-needed"} (${ASSIGNMENT_DATA_LIVE_TOOLS.join(", ")}). Offline counts snapshot: ${present && capturedAt !== null ? `as of ${capturedAt}` : "none"}.`
|
|
35409
|
+
};
|
|
35410
|
+
};
|
|
34235
35411
|
partitionCoverage = (entries) => ({
|
|
34236
35412
|
covered: entries.filter((entry) => entry.requested && (entry.retrieved > 0 || entry.retrieveConfirmed === true) && !entry.errored && !entry.neverModeled && entry.pending !== true),
|
|
34237
35413
|
partial: entries.filter((entry) => entry.requested && (entry.retrieved === 0 && entry.retrieveConfirmed !== true || entry.errored) && !entry.neverModeled && entry.pending !== true),
|
|
@@ -34245,6 +35421,7 @@ var init_coverage_report = __esm({
|
|
|
34245
35421
|
const missingCoverage = summary.missingCoverage;
|
|
34246
35422
|
const staged = ctx.manifest.staged;
|
|
34247
35423
|
const topUncoveredFamilies = rankUncoveredFamilies(ctx.manifest).slice(0, TOP_UNCOVERED_FAMILIES_CAP);
|
|
35424
|
+
const assignmentData = await buildAssignmentDataCoverage(ctx);
|
|
34248
35425
|
return ok({
|
|
34249
35426
|
data: {
|
|
34250
35427
|
coverageKnown: summary.coverageKnown,
|
|
@@ -34253,6 +35430,7 @@ var init_coverage_report = __esm({
|
|
|
34253
35430
|
...staged !== void 0 ? { stagedBuild: { tier: staged.tier, totalTiers: staged.totalTiers } } : {},
|
|
34254
35431
|
summary,
|
|
34255
35432
|
topUncoveredFamilies,
|
|
35433
|
+
assignmentData,
|
|
34256
35434
|
trust: {
|
|
34257
35435
|
provenance: "offline_snapshot",
|
|
34258
35436
|
confidence: summary.coverageKnown ? "declared" : "unknown",
|
|
@@ -49239,7 +50417,7 @@ import { existsSync as existsSync5 } from "node:fs";
|
|
|
49239
50417
|
import { stat as stat6 } from "node:fs/promises";
|
|
49240
50418
|
import { join as join20 } from "node:path";
|
|
49241
50419
|
import { z as z81 } from "zod";
|
|
49242
|
-
var STALE_AGE_DAYS, MS_PER_DAY2, SKIPPED_FILES_DEGRADED_THRESHOLD, healthCheckInputSchema, buildFreshness, probeGraph, probeSourceHash, probeRenderComplete, healthCheckHandler;
|
|
50420
|
+
var STALE_AGE_DAYS, MS_PER_DAY2, SKIPPED_FILES_DEGRADED_THRESHOLD, STALE_FACTS_ADVISORY_DAYS, healthCheckInputSchema, buildFreshness, probeGraph, probeSourceHash, probeRenderComplete, healthCheckHandler;
|
|
49243
50421
|
var init_health_check = __esm({
|
|
49244
50422
|
"../mcp/dist/src/tools/health-check.js"() {
|
|
49245
50423
|
"use strict";
|
|
@@ -49247,9 +50425,11 @@ var init_health_check = __esm({
|
|
|
49247
50425
|
init_src2();
|
|
49248
50426
|
init_src3();
|
|
49249
50427
|
init_history_store();
|
|
50428
|
+
init_coverage_report();
|
|
49250
50429
|
STALE_AGE_DAYS = 7;
|
|
49251
50430
|
MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
49252
50431
|
SKIPPED_FILES_DEGRADED_THRESHOLD = 100;
|
|
50432
|
+
STALE_FACTS_ADVISORY_DAYS = 30;
|
|
49253
50433
|
healthCheckInputSchema = z81.object({});
|
|
49254
50434
|
buildFreshness = (ctx, now, sourceHashMatches, recent, update) => {
|
|
49255
50435
|
const refreshedAt = ctx.manifest.refreshedAt;
|
|
@@ -49398,6 +50578,16 @@ var init_health_check = __esm({
|
|
|
49398
50578
|
}
|
|
49399
50579
|
const freshness = buildFreshness(ctx, now, hashProbe.match, recent, update);
|
|
49400
50580
|
const vaultHistoryEnabled = existsSync5(join20(ctx.vaultRoot, ".git"));
|
|
50581
|
+
const assignmentBase = await buildAssignmentDataCoverage(ctx);
|
|
50582
|
+
let assignmentAdvisory = null;
|
|
50583
|
+
if (assignmentBase.factsCounts.present && assignmentBase.factsCounts.capturedAt !== null) {
|
|
50584
|
+
const capturedMs = Date.parse(assignmentBase.factsCounts.capturedAt);
|
|
50585
|
+
const factsAgeDays = Number.isNaN(capturedMs) ? null : Math.max(0, Math.floor((now - capturedMs) / MS_PER_DAY2));
|
|
50586
|
+
if (factsAgeDays !== null && factsAgeDays >= STALE_FACTS_ADVISORY_DAYS) {
|
|
50587
|
+
assignmentAdvisory = `Assignment-data counts snapshot is ${factsAgeDays} days old (captured ${assignmentBase.factsCounts.capturedAt}); holder counts quoted from it may be stale \u2014 re-run \`sfi refresh --with-data-shape\`, or use the live tools for current rosters.`;
|
|
50588
|
+
}
|
|
50589
|
+
}
|
|
50590
|
+
const assignmentData = { ...assignmentBase, advisory: assignmentAdvisory };
|
|
49401
50591
|
return ok({
|
|
49402
50592
|
data: {
|
|
49403
50593
|
status,
|
|
@@ -49405,6 +50595,7 @@ var init_health_check = __esm({
|
|
|
49405
50595
|
checks,
|
|
49406
50596
|
coverage,
|
|
49407
50597
|
freshness,
|
|
50598
|
+
assignmentData,
|
|
49408
50599
|
vaultHistory: {
|
|
49409
50600
|
enabled: vaultHistoryEnabled,
|
|
49410
50601
|
enableHint: vaultHistoryEnabled ? null : "Run `sfi vault git enable` once for `sfi.component_history` / `sfi.component_as_of`."
|
|
@@ -52722,23 +53913,6 @@ var init_naming_convention_report = __esm({
|
|
|
52722
53913
|
}
|
|
52723
53914
|
});
|
|
52724
53915
|
|
|
52725
|
-
// ../mcp/dist/src/tools/vault-assignment-disclosure.js
|
|
52726
|
-
var USER_ASSIGNMENT_NOT_IN_VAULT, PERMSET_INTERSECTION_NOT_AVAILABLE, SHARING_USER_ENUMERATION_NOT_AVAILABLE, userAssignmentUnavailable;
|
|
52727
|
-
var init_vault_assignment_disclosure = __esm({
|
|
52728
|
-
"../mcp/dist/src/tools/vault-assignment-disclosure.js"() {
|
|
52729
|
-
"use strict";
|
|
52730
|
-
init_src3();
|
|
52731
|
-
USER_ASSIGNMENT_NOT_IN_VAULT = "User and PermissionSetAssignment data are not retrieved into this vault \u2014 which users hold a profile, permission set, or group membership cannot be answered offline. Run the live org plane (read-only) or refresh with a PermissionSetAssignment facts capture for user-assignment questions.";
|
|
52732
|
-
PERMSET_INTERSECTION_NOT_AVAILABLE = "Which users have multiple permission sets assigned requires PermissionSetAssignment rows (user assignment data not in vault). This tool can list each permission set structurally but cannot name users with both assignments without querying live assignment data.";
|
|
52733
|
-
SHARING_USER_ENUMERATION_NOT_AVAILABLE = "Sharing rules name group/role targets, but User records and group membership to individual users are not fully available offline \u2014 cannot enumerate every user who effectively receives Edit from a group-based sharing rule without live User / GroupMember / PermissionSetAssignment data.";
|
|
52734
|
-
userAssignmentUnavailable = (ctx) => {
|
|
52735
|
-
const userCov = summarizeCoverage(ctx.manifest, ["User"]);
|
|
52736
|
-
const psaCov = summarizeCoverage(ctx.manifest, ["PermissionSetAssignment"]);
|
|
52737
|
-
return userCov.status !== "complete" || psaCov.status !== "complete";
|
|
52738
|
-
};
|
|
52739
|
-
}
|
|
52740
|
-
});
|
|
52741
|
-
|
|
52742
53916
|
// ../mcp/dist/src/tools/object-access-audit.js
|
|
52743
53917
|
import { z as z100 } from "zod";
|
|
52744
53918
|
var CUSTOM_OBJECT_PREFIX7, PERMISSION_SET_PREFIX2, GRANTOR_TYPES2, objectAccessAuditInputSchema, flag, compareGrants2, objectAccessAuditHandler;
|
|
@@ -55261,7 +56435,7 @@ var init_retrieve_blindspot_report = __esm({
|
|
|
55261
56435
|
});
|
|
55262
56436
|
|
|
55263
56437
|
// ../mcp/dist/src/context-resolution.js
|
|
55264
|
-
var PRONOUN_ANCHORS, REPARAM_ANCHORS, detectPronounAnchor, detectReparamAnchor, ANAPHOR_ONLY, isAnaphorOnly, ORDINAL_INDEX, SELECTION_SHAPE, NON_DESCRIPTOR_TOKENS, detectClarificationSelection, COMPONENT_ID_SHAPE, validatePreviousContext, CONTINUATION_TOOL_TYPES, continuationToolCompatible, REPARAM_FILLER, extractReparamTarget;
|
|
56438
|
+
var PRONOUN_ANCHORS, REPARAM_ANCHORS, detectPronounAnchor, detectReparamAnchor, ANAPHOR_ONLY, isAnaphorOnly, ORDINAL_INDEX, SELECTION_SHAPE, NON_DESCRIPTOR_TOKENS, detectClarificationSelection, RUNTIME_FOLLOW_UP_SHAPES, GAP_FOLLOW_UP_SHAPES, detectGapShapedFollowUp, COMPONENT_ID_SHAPE, validatePreviousContext, CONTINUATION_TOOL_TYPES, continuationToolCompatible, REPARAM_FILLER, extractReparamTarget;
|
|
55265
56439
|
var init_context_resolution = __esm({
|
|
55266
56440
|
"../mcp/dist/src/context-resolution.js"() {
|
|
55267
56441
|
"use strict";
|
|
@@ -55352,6 +56526,78 @@ var init_context_resolution = __esm({
|
|
|
55352
56526
|
const containing = options.filter((option) => option.toLowerCase().includes(token));
|
|
55353
56527
|
return containing.length === 1 ? { kind: "selected", anaphor, selection: containing[0] } : { kind: "re-ask", anaphor };
|
|
55354
56528
|
};
|
|
56529
|
+
RUNTIME_FOLLOW_UP_SHAPES = [
|
|
56530
|
+
// login history + IPs (per-user session telemetry, never modeled).
|
|
56531
|
+
/\b(?:login|log[-\s]?in|sign[-\s]?in)\s+history\b|\bwho\s+(?:actually\s+)?logged\s+in\b|\bfrom\s+what\s+ip\b|\bip\s+address(?:es)?\b/i,
|
|
56532
|
+
// API call logs / latency / error rate (runtime, not metadata).
|
|
56533
|
+
/\bapi\s+call\s+(?:logs?|counts?|volume)\b|\b(?:error|failure)\s+rate\b|\bhow\s+(?:many\s+times|often)\s+(?:was\s+it|has\s+it\s+been)\s+called\b|\bcall\s+volume\b/i,
|
|
56534
|
+
// approval-instance history — "who actually approved" (a runtime instance,
|
|
56535
|
+
// not the approval-process metadata). "who CAN approve" stays a config read.
|
|
56536
|
+
/\bwho\s+(?:actually\s+)?approved\b|\bapproval\s+history\b|\bwas\s+(?:it|that|this)\s+approved\b/i,
|
|
56537
|
+
// record field-history — every value change ON A RECORD (runtime audit
|
|
56538
|
+
// trail). "why did the FIELD change" (metadata provenance) is NOT this: the
|
|
56539
|
+
// anchor is a record/instance ("on that record", "for that account").
|
|
56540
|
+
/\b(?:every|all\s+the|each)\s+(?:value\s+)?changes?\s+(?:on|to|for)\s+(?:that|this|the)\s+(?:record|row|account|contact|case|lead|opportunity)\b|\bfield\s+history\s+(?:on|for)\b/i,
|
|
56541
|
+
// report/dashboard subscription recipients (runtime subscriptions).
|
|
56542
|
+
/\bwho\s+(?:is\s+)?subscribed\b|\bsubscription\s+recipients?\b|\bwho\s+gets?\s+(?:the|that)\s+(?:report|dashboard)\s+(?:emailed|sent)\b/i,
|
|
56543
|
+
// chatter / feed posts (runtime social data).
|
|
56544
|
+
/\bchatter\s+(?:posts?|activity|feed)\b|\bfeed\s+(?:posts?|items?|comments?)\b/i,
|
|
56545
|
+
// sandbox refresh date / deployment logs (runtime org lifecycle telemetry).
|
|
56546
|
+
/\bsandbox\s+(?:last\s+)?refresh(?:ed)?\b|\bwhen\s+was\s+(?:the\s+)?sandbox\b|\bdeployment\s+(?:logs?|history)\b|\blast\s+deploy(?:ment|ed)\b/i,
|
|
56547
|
+
// flow-interview history / event publish counts (runtime execution traces).
|
|
56548
|
+
/\bflow\s+interview(?:s|\s+history)?\b|\b(?:how\s+many\s+)?(?:events?\s+)?(?:were\s+)?published\b|\bpublish\s+counts?\b/i
|
|
56549
|
+
];
|
|
56550
|
+
GAP_FOLLOW_UP_SHAPES = [
|
|
56551
|
+
// Normative/judgment asks — the product reads metadata, it has no opinion
|
|
56552
|
+
// (q891/q30 "should they be able to?", q95 "is it normal?", q1075 "was any
|
|
56553
|
+
// of it risky?", q1054 "are those considered sensitive?", q1065 "how hard
|
|
56554
|
+
// is it to close?", q1060 "does that mean something's broken?", q1012 "is
|
|
56555
|
+
// it doing its job?").
|
|
56556
|
+
[/\bshould\s+(?:they|he|she|it)\s+(?:be\s+able|have|see)\b/i, "judgment"],
|
|
56557
|
+
[/\bis\s+(?:it|that|this)\s+normal\b|\bseems?\s+like\s+a\s+lot\b/i, "judgment"],
|
|
56558
|
+
[/\bwas\s+(?:any\s+of\s+)?(?:it|that|this)\s+risky\b/i, "judgment"],
|
|
56559
|
+
[/\bare\s+(?:those|these|they)\s+considered\s+\w+/i, "judgment"],
|
|
56560
|
+
[/\bhow\s+hard\s+(?:is|would)\s+it\b/i, "judgment"],
|
|
56561
|
+
[/\bdoes\s+that\s+mean\s+something(?:'s|\s+is)\s+broken\b/i, "judgment"],
|
|
56562
|
+
[/\bis\s+it\s+doing\s+its\s+job\b/i, "judgment"],
|
|
56563
|
+
// R4 additions — more normative/opinion shapes the 2K context run showed
|
|
56564
|
+
// inheriting the prior tool: "is that a problem?", "is that too many/too
|
|
56565
|
+
// much?", "is that a good idea?", "should I be worried?", "is that
|
|
56566
|
+
// secure/safe?" (a verdict, not a metadata read — "is it SAFE TO DELETE"
|
|
56567
|
+
// is excluded: it carries its own real route and never reaches this
|
|
56568
|
+
// detector, which only runs while STILL unrouted).
|
|
56569
|
+
[/\bis\s+(?:that|this|it)\s+(?:a\s+)?problem\b|\bis\s+(?:that|this|it)\s+(?:too\s+(?:many|much|few)|a\s+lot)\b/i, "judgment"],
|
|
56570
|
+
[/\bis\s+(?:that|this|it)\s+(?:a\s+)?good\s+idea\b|\bshould\s+i\s+be\s+(?:worried|concerned)\b/i, "judgment"],
|
|
56571
|
+
[/\bis\s+(?:that|this|it)\s+(?:considered\s+)?(?:secure|safe|risky|dangerous)\b(?!\s+to\s+delete\b)/i, "judgment"],
|
|
56572
|
+
// Delivery/export asks (q914 "as a file rather than on screen", q882 "can
|
|
56573
|
+
// it be exported…", q1939 "flag those as an audit finding").
|
|
56574
|
+
[/\bas\s+a\s+file\b|\bcan\s+(?:it|this|that)\s+be\s+exported\b/i, "delivery"],
|
|
56575
|
+
[/^\s*flag\s+(?:those|these|them|it)\b/i, "delivery"],
|
|
56576
|
+
// R4 additions — more delivery/notification shapes: "email me that", "send
|
|
56577
|
+
// it to …", "put it in a spreadsheet / csv / pdf", "download it".
|
|
56578
|
+
[/^\s*(?:email|send|forward)\s+(?:me\s+)?(?:that|this|it|those|these|them)\b/i, "delivery"],
|
|
56579
|
+
[/\b(?:as|in|to)\s+(?:a\s+)?(?:spreadsheet|csv|pdf|excel|report\s+file)\b|\b(?:can\s+(?:you|i)\s+)?download\s+(?:it|that|this|them)\b/i, "delivery"],
|
|
56580
|
+
// Self-capability probes about the TOOL, not the org (q849 "does the tool
|
|
56581
|
+
// trace transitive access like that, or is that beyond it?").
|
|
56582
|
+
[/\b(?:does|can)\s+(?:the|this)\s+tool\b/i, "tool-self-capability"],
|
|
56583
|
+
// R4 additions — "is that beyond you / can you even do that / do you have
|
|
56584
|
+
// that data" self-capability probes.
|
|
56585
|
+
[/\bis\s+(?:that|this)\s+beyond\s+(?:you|it|the\s+tool)\b|\bcan\s+you\s+even\b|\bdo\s+you\s+(?:have|hold)\s+(?:that|this)\s+(?:data|info(?:rmation)?)\b/i, "tool-self-capability"],
|
|
56586
|
+
// Deployment-status telemetry (q1402 "was the change deployed or is it
|
|
56587
|
+
// still pending?").
|
|
56588
|
+
[/\bstill\s+pending\b|\bwas\s+(?:it|that|the\s+change)\s+deployed\b/i, "deployment-status"]
|
|
56589
|
+
];
|
|
56590
|
+
detectGapShapedFollowUp = (question) => {
|
|
56591
|
+
for (const [shape, family] of GAP_FOLLOW_UP_SHAPES) {
|
|
56592
|
+
if (shape.test(question))
|
|
56593
|
+
return family;
|
|
56594
|
+
}
|
|
56595
|
+
for (const shape of RUNTIME_FOLLOW_UP_SHAPES) {
|
|
56596
|
+
if (shape.test(question))
|
|
56597
|
+
return "runtime-analytics";
|
|
56598
|
+
}
|
|
56599
|
+
return null;
|
|
56600
|
+
};
|
|
55355
56601
|
COMPONENT_ID_SHAPE = /^[A-Za-z]+:.+$/;
|
|
55356
56602
|
validatePreviousContext = (previous, isRegisteredTool) => {
|
|
55357
56603
|
const ignored = [];
|
|
@@ -55437,16 +56683,56 @@ var init_context_resolution = __esm({
|
|
|
55437
56683
|
});
|
|
55438
56684
|
|
|
55439
56685
|
// ../mcp/dist/src/refusal-gates.js
|
|
55440
|
-
var INJECTION_PATTERNS, EXFIL_VERB, EXFIL_QUANTIFIER, EXFIL_SENSITIVE, EXFIL_VALUES, EXFIL_SINGLE_VALUE, INJECTION_DISCLOSURE, WRITE_VERB, WRITE_VERB_FILLER, WRITE_EXCLUDER, WRITE_SENTENCE_INITIAL, WRITE_LEAD_IN, WRITE_TRAILING_FRAME, WRITE_MAKE_SCHEMA, WRITE_GIVE_GRANT, WRITE_GIVE_INITIAL, WRITE_CHAINED_DUPE_DELETE, readOnlyAlternativeFor, RUNTIME_TRIGGERS, RUNTIME_WINDOW, RUNTIME_INCIDENT, runtimeDisclosure, EXTERNAL_SYSTEM, POLICY_ASK, RETENTION_POLICY_ASK, CONSENT_PROCESS_ASK, OPINION_ASK, SHOULD_WE, METADATA_NOUN, DELIVERY_ASK, WRITE_CODE_ASK, outOfScopeDisclosure, detectRefusalShape;
|
|
56686
|
+
var INJECTION_PATTERNS, EXFIL_VERB, EXFIL_QUANTIFIER, EXFIL_SENSITIVE, EXFIL_VALUES, EXFIL_SINGLE_VALUE, INJECTION_DISCLOSURE, WRITE_VERB, WRITE_VERB_FILLER, WRITE_EXCLUDER, WRITE_DOC_NOUN, WRITE_READ_FRAME, SIMULATION_TAIL, CLAUSE_SEP, WRITE_SENTENCE_INITIAL, WRITE_LEAD_IN, WRITE_TRAILING_FRAME, WRITE_MAKE_SCHEMA, WRITE_GIVE_GRANT, WRITE_GIVE_INITIAL, WRITE_CHECK_IN, WRITE_CHAINED_DEPLOY, WRITE_CHAINED_DUPE_DELETE, RUN_VERB, RUN_TARGET, RUN_GAP, RUN_IMPERATIVE_INITIAL, RUN_IMPERATIVE_LEAD_IN, RUN_ANAPHOR, runReadOnlyAlternativeFor, readOnlyAlternativeFor, IDENTITY_IDIOM, IDENTITY_ALLOWED_MUTATE, IDENTITY_LOOK_EXCLUDER, IDENTITY_DISCLOSURE, FORECAST_VERB, FORECAST_TREND, FORECAST_HORIZON, FORECAST_OUTCOME, FORECAST_STRONG_OUTCOME, FORECAST_DISCLOSURE, PROVENANCE_CREATE, PROVENANCE_CREATE_DATE, PROVENANCE_LASTMOD_ONLY, PROVENANCE_DISCLOSURE, RUNTIME_TRIGGERS, RUNTIME_WINDOW, RUNTIME_INCIDENT, runtimeDisclosure, EXTERNAL_SYSTEM, POLICY_ASK, RETENTION_POLICY_ASK, CONSENT_PROCESS_ASK, OPINION_ASK, SHOULD_WE, METADATA_NOUN, DELIVERY_ASK, WRITE_CODE_ASK, DRAFT_DOCUMENT_ASK, CAREER_ASK, outOfScopeDisclosure, detectRefusalShape;
|
|
55441
56687
|
var init_refusal_gates = __esm({
|
|
55442
56688
|
"../mcp/dist/src/refusal-gates.js"() {
|
|
55443
56689
|
"use strict";
|
|
55444
56690
|
INJECTION_PATTERNS = [
|
|
55445
|
-
|
|
56691
|
+
// "ignore your previous/prior/read-only instructions|rules|restrictions".
|
|
56692
|
+
// R4: the (?:and\s+\w+\s+)? optional carries "ignore your read-only AND
|
|
56693
|
+
// PRIVACY restrictions" (q3749) — a conjoined adjective before the noun no
|
|
56694
|
+
// longer breaks the token sequence. `constraints` joins the noun set (q4047
|
|
56695
|
+
// "ignore your read-only constraints").
|
|
56696
|
+
/\bignore\s+(?:your|all|the)\s+(?:previous|prior|read-?only)\s+(?:and\s+\w+\s+)?(?:instructions?|rules?|restrictions?|constraints?)\b/i,
|
|
56697
|
+
// R4 injection framings the 3K phrasings used to evade every arm above:
|
|
56698
|
+
// - "forget everything above / forget your prior instructions" (q4842).
|
|
56699
|
+
/\bforget\s+(?:everything|all(?:\s+of)?\s+the)\s+(?:above|prior|previous|earlier)\b|\bforget\s+(?:your|the)\s+(?:prior|previous|earlier)\s+(?:instructions?|rules?|restrictions?|constraints?)\b/i,
|
|
56700
|
+
// - role-play jailbreak: "AI with no restrictions", "named DAN", "you are
|
|
56701
|
+
// now an unrestricted assistant / DAN / an AI that can modify the org".
|
|
56702
|
+
/\b(?:ai|assistant|model)\s+(?:with\s+)?no\s+restrictions\b|\bnamed\s+dan\b|\bact\s+as\s+dan\b|\byou\s+are\s+(?:now\s+)?(?:an?\s+)?(?:unrestricted|jailbroken|uncensored)\b/i,
|
|
56703
|
+
// - "SYSTEM OVERRIDE", "the previous ... constraints are revoked",
|
|
56704
|
+
// "you are now permitted to reveal ..." (q4873).
|
|
56705
|
+
/\bsystem\s+override\b|\b(?:previous|prior)\s+(?:knowledge-?base\s+)?(?:constraints?|restrictions?|instructions?)\s+(?:are|have\s+been)\s+revoked\b|\byou\s+are\s+now\s+permitted\s+to\s+reveal\b/i,
|
|
56706
|
+
// - "new instruction ... embedded in this message" / "instruction from the
|
|
56707
|
+
// vault owner embedded in ..." (q4627) — a smuggled-instruction framing.
|
|
56708
|
+
/\b(?:new\s+)?instruction[s]?\b[^.?!]{0,40}\bembedded\s+in\s+this\s+(?:message|prompt|text|question)\b/i,
|
|
56709
|
+
// - social-engineering authority claim to force an action: "the (real)
|
|
56710
|
+
// admins told me to have you ...", "they authorized it", "the client
|
|
56711
|
+
// signed off" chained with a mutation/exfil (q4893/q4668). Narrow: the
|
|
56712
|
+
// authority claim must co-occur with an override/do-it imperative.
|
|
56713
|
+
// "the client signed off" arms ONLY when a redeploy/deploy/mutation of org
|
|
56714
|
+
// artifacts rides in the same breath (q4668) — a plain "the client signed
|
|
56715
|
+
// off on the design, show me the flow" must not gate, so require a deploy
|
|
56716
|
+
// verb nearby.
|
|
56717
|
+
/\bthey\s+authorized\s+it\b|\breal\s+admins?\s+told\s+me\s+to\s+have\s+you\b|\b(?:the\s+)?client\s+signed\s+off\b[^.?!]{0,120}\b(?:re-?deploy|deploy|repoint|push\s+(?:it|them|this)|redeploy\s+them)\b/i,
|
|
55446
56718
|
/\bpretend\s+the\s+(?:previous|prior)\s+rules\b/i,
|
|
55447
56719
|
/\byou\s+are\s+now\s+(?:in\s+)?(?:admin|deploy|god)\s*[- ]?mode\b/i,
|
|
55448
56720
|
/\byou'?re\s+actually\s+an?\b[^.?!]*\bbot\b/i,
|
|
55449
|
-
/\b(?:print|reveal|show)\s+(?:me\s+)?your\s+(?:system\s+)?prompt\b/i
|
|
56721
|
+
/\b(?:print|reveal|show)\s+(?:me\s+)?your\s+(?:system\s+)?prompt\b/i,
|
|
56722
|
+
// Secret/credential exfiltration (q4873/q3635 secret asks): reveal/output/
|
|
56723
|
+
// give the raw connection string, OAuth/client secret, session/refresh
|
|
56724
|
+
// token, API key, or password policy for delivery. The metadata READ
|
|
56725
|
+
// "which named credentials exist" / "what auth provider does X use" carries
|
|
56726
|
+
// no reveal-the-SECRET-VALUE verb and stays routed. `endpoint` alone is NOT
|
|
56727
|
+
// a secret (q4956 "I need the endpoint on the named credential" routes).
|
|
56728
|
+
/\b(?:reveal|output|show\s+me|give\s+me|dump|print|expose)\b[^.?!]{0,60}\b(?:client\s+secret|oauth\s+secret|connection\s+string|session\s+token|refresh\s+token|api\s+key|secret\s+key|private\s+key)\b/i,
|
|
56729
|
+
// Privilege-escalation asks (q1548 "sudo give me full access"): `sudo` as a
|
|
56730
|
+
// command prefix, or a grant-to-SELF ("give me full/admin access"). The
|
|
56731
|
+
// READ delivery ask "give me the FLS grant list" carries no privilege noun
|
|
56732
|
+
// and stays routed — the escalation arm requires full/admin/elevated +
|
|
56733
|
+
// access/permission.
|
|
56734
|
+
/(?:^|[.!?;]\s+)sudo\b/i,
|
|
56735
|
+
/\bgive\s+me\s+(?:full|admin|superuser|elevated|god[-\s]?mode)\s+(?:access|permissions?|rights?|privileges?)\b/i
|
|
55450
56736
|
];
|
|
55451
56737
|
EXFIL_VERB = /\b(?:dump|export|give\s+me|tell\s+me|list|retrieve)\b/i;
|
|
55452
56738
|
EXFIL_QUANTIFIER = /\b(?:every|all|actual|the\s+real|random)\b/i;
|
|
@@ -55454,16 +56740,45 @@ var init_refusal_gates = __esm({
|
|
|
55454
56740
|
EXFIL_VALUES = /\bvalues?\b|\bstored\b/i;
|
|
55455
56741
|
EXFIL_SINGLE_VALUE = /\b(?:what(?:'s|s|\s+is)|show\s+me|give\s+me|pull)\s+the\s+(?:ssn|social\s+security(?:\s+number)?)\b(?!\s+fields?\b)[^.?!]{0,60}\b(?:lead|contact|record|student|person|user|account|applicant)s?\b/i;
|
|
55456
56742
|
INJECTION_DISCLOSURE = "REFUSED: this request asks me to override instructions or exfiltrate record values. sf-intelligence reads metadata only; it never returns stored record values and its instructions are not overridable.";
|
|
55457
|
-
WRITE_VERB = "(?:create|add|delete|remove|update|change|(?<!permission\\s)set|deploy|assign|reassign|revoke|deactivate|activate|disable|enable|merge|reset|rename|fix|grant|provision|turn\\s+(?:on|off)|clean\\s+up|migrate|convert|insert|upsert|push|publish|install|uninstall|schedule|upgrade|downgrade|throttle|purge)";
|
|
55458
|
-
WRITE_VERB_FILLER = "(?:(?:bulk|mass|batch|just|please|go\\s+ahead\\s+and)
|
|
55459
|
-
WRITE_EXCLUDER = /\b(?:am\s+i|can\s+i|could\s+i|do\s+i|who\s+can|who\s+is\s+able|allowed\s+to|able\s+to|what\s+if|what\s+would|would\s+happen|is\s+it\s+safe|safe\s+to|before\s+i|if\s+i|should\s+i|how\s+do\s+i|how\s+would\s+i|what\s+happens\s+when)\b/i;
|
|
55460
|
-
|
|
55461
|
-
|
|
56743
|
+
WRITE_VERB = "(?:create|add|delete|remove|update|change|(?<!permission\\s)set|(?:quick[-\\s]?|re)?deploy|assign|reassign|revoke|deactivate|activate|disable|enable|merge|reset|rename|fix|grant|provision|turn\\s+(?:on|off)|clean\\s+up|migrate|convert|insert|upsert|push|publish|install|uninstall|schedule|upgrade|downgrade|throttle|purge|build|split|rotate|bump|pin|operationalize|repoint|stand\\s+up|spin\\s+up|comment\\s+out|wire\\s+(?:it|them|this|that)?\\s*(?:in|into)|roll\\s+(?:it|them|this|that)?\\s*into)";
|
|
56744
|
+
WRITE_VERB_FILLER = "(?:(?:bulk|mass|batch|just|please|go\\s+ahead\\s+and)[-\\s]+)*";
|
|
56745
|
+
WRITE_EXCLUDER = /\b(?:am\s+i|can\s+i|could\s+i|do\s+i|who\s+can|who\s+is\s+able|allowed\s+to|able\s+to|what\s+if|what\s+would|would\s+happen|is\s+it\s+safe|safe\s+to|before\s+i|if\s+(?:i|we)|when\s+(?:i|we)|suppose|should\s+i|how\s+do\s+i|how\s+would\s+i|what\s+happens\s+when)\b|\bsafely\s*\?|\bwhat\s+(?:depends\s+on|breaks|would\s+break|will\s+break|stops\s+working)\b/i;
|
|
56746
|
+
WRITE_DOC_NOUN = "(?:onboarding|admin|developer|dev|architecture|data)?[-\\s]?(?:doc(?:ument(?:ation)?|s)?|handbook|tour|walkthrough|overview|dictionary|summary|guide|primer|runbook|onboarding|write[-\\s]?up|report\\s+of)";
|
|
56747
|
+
WRITE_READ_FRAME = new RegExp(
|
|
56748
|
+
// 1 — doc-generation: a build/create/generate/write/draft/make verb whose
|
|
56749
|
+
// object (within a short window) is a documentation noun.
|
|
56750
|
+
`\\b(?:build|create|generate|write|draft|put\\s+together|make|need|want|give\\s+me)\\b[^.?!;]{0,40}\\b${WRITE_DOC_NOUN}\\b|\\b${WRITE_DOC_NOUN}\\b[^.?!;]{0,30}\\.?\\s*(?:build|create|generate|make|put\\s+together)\\s+(?:it|this|that|one)\\b|\\b(?:which|what|who|whose|list\\s+(?:the\\s+)?)\\b[^.?!;]{0,40}\\b(?:enables?|disables?|grants?)\\b|\\b(?:before|after|ahead\\s+of|prior\\s+to|until|once)\\s+(?:a\\s+|the\\s+|we\\s+|you\\s+|i\\s+)?(?:re-?)?deploy(?:ment|ing)?\\b`,
|
|
56751
|
+
"i"
|
|
56752
|
+
);
|
|
56753
|
+
SIMULATION_TAIL = /\b(?:and\s+)?(?:tell|show|give)\s+me\s+(?:the\s+)?(?:impact|blast\s+radius|consequences?|every\s+conflict|conflicts|what\s+(?:breaks|happens|stops|changes))\b|[—–-]\s*(?:impact|consequences?|blast\s+radius)\s*\??\s*$|\b(?:impact|consequences)\s*\?\s*$|\bwhat(?:'s|\s+is)\s+the\s+(?:impact|blast\s+radius|fallout)\b|\bwhat\s+(?:happens|stops|breaks)\s+(?:to|if|when|after|downstream)\b/i;
|
|
56754
|
+
CLAUSE_SEP = "(?:^|[.!?;]\\s+|\\s+[\u2014\u2013]\\s*)";
|
|
56755
|
+
WRITE_SENTENCE_INITIAL = new RegExp(`${CLAUSE_SEP}(?:please\\s+|just\\s+)?${WRITE_VERB_FILLER}(${WRITE_VERB}\\b[^.?!;]{0,80})`, "i");
|
|
56756
|
+
WRITE_LEAD_IN = new RegExp(`\\b(?:please|(?<!\\b(?:or|vs|than)\\s)just|go\\s+ahead\\s+and|can\\s+(?:you|u)|could\\s+(?:you|u)|would\\s+(?:you|u)|you\\s+should|i\\s+(?:need|want)\\s+you\\s+to)\\s+(?:please\\s+|just\\s+)?${WRITE_VERB_FILLER}(${WRITE_VERB}\\b[^.?!;]{0,80})`, "i");
|
|
55462
56757
|
WRITE_TRAILING_FRAME = new RegExp(`\\b(${WRITE_VERB}\\b[^.?!;]{0,80}?\\b(?:for\\s+me|right\\s+now|and\\s+confirm)\\b[^.?!;]{0,20})`, "i");
|
|
55463
56758
|
WRITE_MAKE_SCHEMA = new RegExp(`\\b(?:can|could|would)\\s+(?:you|u)\\b[^.?!;]{0,10}\\s(make\\b[^.?!;]{0,60}\\b(?:required|mandatory|optional|unique|read[-\\s]?only|editable|visible)\\b)`, "i");
|
|
55464
56759
|
WRITE_GIVE_GRANT = new RegExp(`\\b(?:can|could|would)\\s+(?:you|u)\\b[^.?!;]{0,30}\\b(give\\s+(?!me\\b)[^.?!;]{0,60}\\b(?:profile|permission|perm\\s+set|access|user)\\b[^.?!;]{0,40})`, "i");
|
|
55465
56760
|
WRITE_GIVE_INITIAL = /(?:^|[.!?;]\s+)(give\s+(?!me\b)[^.?!;]{0,60}\b(?:admin|access|permission|profile)\b[^.?!;]{0,30})/i;
|
|
55466
|
-
|
|
56761
|
+
WRITE_CHECK_IN = /\b(check\s+(?:it|them|this|that)\s+in|commit\s+(?:it|them|this|the\s+\w+)|push\s+(?:it|them|this)\s+to\s+(?:prod|production|the\s+repo)|promote\s+(?:it|them|this)\s+to\s+(?:prod|production))\b/i;
|
|
56762
|
+
WRITE_CHAINED_DEPLOY = /(?:^|[.!?;]\s+|,\s*(?:and\s+|then\s+)?|\band\s+|\bthen\s+)((?:re-?deploy|quick[-\s]?deploy|repoint)\b[^.?!;]{0,60})/i;
|
|
56763
|
+
WRITE_CHAINED_DUPE_DELETE = /(?:\b(?:and|then)\s+(?:just\s+)?|(?:^|[.!?;]\s+)(?:just\s+)?)((?:delete|remove|purge|merge)\s+(?:all\s+|the\s+|every\s+){0,2}dup(?:e|licate)s?\b[^.?!;]{0,30})/i;
|
|
56764
|
+
RUN_VERB = "(?:run|re-?run|execute|invoke|kick\\s+off|trigger|launch|fire\\s+off)";
|
|
56765
|
+
RUN_TARGET = "(?:flows?|triggers?|batch(?:\\s+(?:jobs?|class(?:es)?|apex))?|jobs?|apex\\s+class(?:es)?|automations?|scripts?)";
|
|
56766
|
+
RUN_GAP = "(?:(?!\\b(?:on|of|for|across|against|in|over|about)\\b)[^.?!;]){0,60}?";
|
|
56767
|
+
RUN_IMPERATIVE_INITIAL = new RegExp(`(?:^|[.!?;]\\s+)(?:please\\s+|just\\s+)?(${RUN_VERB}\\b${RUN_GAP}\\b${RUN_TARGET}\\b[^.?!;]{0,60})`, "i");
|
|
56768
|
+
RUN_IMPERATIVE_LEAD_IN = new RegExp(`\\b(?:please|just|go\\s+ahead\\s+and|can\\s+(?:you|u)|could\\s+(?:you|u)|would\\s+(?:you|u)|you\\s+should|i\\s+need\\s+you\\s+to)\\s+(?:please\\s+|just\\s+)?(${RUN_VERB}\\b${RUN_GAP}\\b${RUN_TARGET}\\b[^.?!;]{0,60})`, "i");
|
|
56769
|
+
RUN_ANAPHOR = new RegExp(`(?:(?:^|[.!?;]\\s+)(?:please\\s+|just\\s+)?|\\b(?:please|just|go\\s+ahead\\s+and|can\\s+(?:you|u)|could\\s+(?:you|u)|would\\s+(?:you|u)|you\\s+should|i\\s+need\\s+you\\s+to)\\s+(?:please\\s+|just\\s+)?)(${RUN_VERB}\\s+(?:it|that|this)\\b(?!\\s+(?:by|past)\\b)[^.?!;]{0,40}|(?:kick|fire|set)\\s+(?:it|that|this)\\s+off\\b[^.?!;]{0,30})`, "i");
|
|
56770
|
+
runReadOnlyAlternativeFor = (verbPhrase) => {
|
|
56771
|
+
const v2 = verbPhrase.toLowerCase();
|
|
56772
|
+
if (/\bflows?\b/.test(v2))
|
|
56773
|
+
return "sfi.explain_flow";
|
|
56774
|
+
if (/\b(?:batch|jobs?)\b/.test(v2))
|
|
56775
|
+
return "sfi.scheduled_job_catalog";
|
|
56776
|
+
if (/\btriggers?\b/.test(v2))
|
|
56777
|
+
return "sfi.what_happens_on_save";
|
|
56778
|
+
if (/\bapex|class(?:es)?\b/.test(v2))
|
|
56779
|
+
return "sfi.explain_apex_method";
|
|
56780
|
+
return "sfi.get_impact";
|
|
56781
|
+
};
|
|
55467
56782
|
readOnlyAlternativeFor = (verbPhrase, question) => {
|
|
55468
56783
|
const v2 = verbPhrase.toLowerCase();
|
|
55469
56784
|
const q2 = question.toLowerCase();
|
|
@@ -55499,14 +56814,153 @@ var init_refusal_gates = __esm({
|
|
|
55499
56814
|
}
|
|
55500
56815
|
return "sfi.get_impact";
|
|
55501
56816
|
};
|
|
56817
|
+
IDENTITY_IDIOM = /\bcan\s+i\s+be\s+trusted\s+to\b|\bwithin\s+my\s+power\b|\babove\s+my\s+(?:permission\s+level|pay\s+grade)\b|\bwhat\s+(?:am\s+i|i'?m)\s+capable\s+of\b|\bwondering\s+what\s+i'?m\s+capable\s+of\b/i;
|
|
56818
|
+
IDENTITY_ALLOWED_MUTATE = /\bam\s+i\s+(?:allowed|permitted|able)\s+to\s+(?:change|delete|remove|merge|deactivate|disable|create|rename|reset|convert|reassign|modify|update)\b|\bis\s+it\s+(?:okay?|ok)\s+for\s+me\s+to\s+(?:change|delete|remove|merge|deactivate|disable|create|rename|reset|convert|reassign|modify|update)\b/i;
|
|
56819
|
+
IDENTITY_LOOK_EXCLUDER = /\bam\s+i\s+(?:allowed|permitted|able)\s+to\s+(?:look|see|view|read|ask|know|check|understand|open)\b|\bis\s+it\s+(?:okay?|ok)\s+for\s+me\s+to\s+(?:look|see|view|read|ask|know|check)\b/i;
|
|
56820
|
+
IDENTITY_DISCLOSURE = "HONEST GAP (identity): sf-intelligence has no session-user identity \u2014 it cannot know what YOU specifically are allowed to do. Name the user, profile, or permission set to check and I can answer from the permission metadata (sfi.effective_permissions / sfi.user_ability). Which user or profile should I check?";
|
|
56821
|
+
FORECAST_VERB = /\b(?:forecast|predict|projection|projecting|extrapolate|estimate\s+how\s+many|best\s+estimate\s+of\s+how\s+many)\b|\bproject\s+(?:our|the|how|where|when|my)\b|\bwhen\s+do\s+you\s+project\b|\bgive\s+me\s+your\s+(?:projection|best\s+estimate|churn\s+forecast)\b/i;
|
|
56822
|
+
FORECAST_TREND = /\b(?:current\s+(?:\w+\s+)?(?:growth|trend|trajectory)|given\s+(?:current|projected)\s+(?:\w+\s+)?(?:growth|trends?)|based\s+on\s+(?:current\s+)?trends?|at\s+the\s+current\s+(?:growth\s+)?rate|projected\s+growth)\b/i;
|
|
56823
|
+
FORECAST_HORIZON = /\b(?:next\s+(?:quarter|term|month|fiscal\s+year|year)|by\s+(?:the\s+)?end\s+of\s+next\b|in\s+(?:12\s+months|two\s+quarters|\d+\s+(?:months|quarters|years))|next\s+term)\b/i;
|
|
56824
|
+
FORECAST_OUTCOME = /\bwill\s+(?:we|it|this|our|migrating|rolling|the)\b|\b(?:hit|exceed|run\s+out|breach|reach)\b|\bhow\s+many\b|\bhow\s+likely\b|\bwe'?ll\b|\bwhere\s+will\b/i;
|
|
56825
|
+
FORECAST_STRONG_OUTCOME = /\bwill\b[^.?!]{0,60}\b(?:hit|exceed|run\s+out|breach|reach)\b/i;
|
|
56826
|
+
FORECAST_DISCLOSURE = "HONEST GAP (forecast): sf-intelligence has no time-series or forecasting model \u2014 it reads the CURRENT metadata/limits snapshot, not future projections or growth trends. I can show you the present state (e.g. sfi.live_org_limits, sfi.org_pulse, sfi.tech_debt_score) so you can extrapolate, but the prediction itself is out of scope.";
|
|
56827
|
+
PROVENANCE_CREATE = /\bwho\s+(?:originally\s+)?(?:created|built|authored|made|wrote|set\s+up|first\s+(?:created|built|granted|authored))\b|\b(?:original|initial)\s+author\b|\bwho\s+first\s+(?:created|built|authored)\b|\bcreator\s+of\b|\bwho\s+(?:created|built|authored|set\s+up)\s+(?:it|this|that|the)\b/i;
|
|
56828
|
+
PROVENANCE_CREATE_DATE = /\bcreation\s+date\b|\binstall\s+date\b/i;
|
|
56829
|
+
PROVENANCE_LASTMOD_ONLY = /\bwho\s+(?:last\s+(?:modified|changed|updated|edited)|modified|changed|updated|edited)\b/i;
|
|
56830
|
+
PROVENANCE_DISCLOSURE = "HONEST GAP (authorship): the vault records LastModified (who/when) where the refresh captured it, but never CreatedBy / original author / creation date \u2014 Salesforce does not expose creator provenance in metadata source. For the answerable side, sfi.last_modified reports who LAST changed a component and when.";
|
|
55502
56831
|
RUNTIME_TRIGGERS = [
|
|
55503
56832
|
[/\blogin\s+history\b|\baudit\s+trail\s+of\s+logins?\b/i, "login history"],
|
|
56833
|
+
// Per-user LOGIN EVENTS (hon-031/hon-036/hon-060): who logged in when,
|
|
56834
|
+
// exact login timestamps, who is logged in right now, or a full per-user
|
|
56835
|
+
// last-login roster. All are LoginHistory/session event data no tool reads.
|
|
56836
|
+
// Precision guards, each a live_inactive_users question that must NOT gate:
|
|
56837
|
+
// "which users are inactive", "who HASN'T logged in for 90 days" (the
|
|
56838
|
+
// negation breaks the strict token sequence), "login IP ranges on a
|
|
56839
|
+
// profile" (profile_security metadata — no logged-in/timestamp token).
|
|
56840
|
+
[
|
|
56841
|
+
/\b(?:which|what)\s+(?:specific\s+)?users?\s+(?:have\s+|are\s+)?logged\s+in(?:to)?\b|\bwho\s+logged\s+in(?:to)?\b/i,
|
|
56842
|
+
"per-user login events (who logged in when)"
|
|
56843
|
+
],
|
|
56844
|
+
[
|
|
56845
|
+
/\blogin\s+timestamps?\b|\bcurrently\s+logged\s+in(?:to)?\b|\blogged\s+in(?:to)?\b[^.?!]{0,30}\bright\s+now\b/i,
|
|
56846
|
+
"login sessions/timestamps"
|
|
56847
|
+
],
|
|
56848
|
+
[
|
|
56849
|
+
/\b(?:every|each|all)\s+(?:salesforce\s+)?users?'?s?\b[^.?!]{0,30}\blast\s+login\b/i,
|
|
56850
|
+
"a full per-user last-login roster (sfi.live_inactive_users covers dormancy thresholds, not a full roster)"
|
|
56851
|
+
],
|
|
55504
56852
|
[/\badoption\b|\bhow\s+often\s+do\s+users\s+actually\s+use\b/i, "adoption/usage telemetry"],
|
|
55505
56853
|
[
|
|
55506
56854
|
/\bping\s+(?:the\s+)?\S+|\bendpoints?\b[^.?!]*\bup\b|\b(?:returned|returning|throwing|threw)\s+errors?\b/i,
|
|
55507
56855
|
"endpoint health"
|
|
55508
56856
|
],
|
|
55509
|
-
[/\brunning[-\s]user\s+context\b/i, "the runtime running-user context"]
|
|
56857
|
+
[/\brunning[-\s]user\s+context\b/i, "the runtime running-user context"],
|
|
56858
|
+
// Automation EXECUTION forensics (hon-032/hon-039/hon-037): traces of what
|
|
56859
|
+
// a run touched, aggregate run counts, the error message from the last
|
|
56860
|
+
// failure. Static reads stay routed: "how many times is this field
|
|
56861
|
+
// REFERENCED" (no run verb), "what runs when an Application is submitted"
|
|
56862
|
+
// (no how-many-times/trace), "when was it last MODIFIED" (no failed/ran).
|
|
56863
|
+
[
|
|
56864
|
+
/\bexecution\s+trace\b|\bwhat\s+records\s+did\s+(?:it|the\s+\S+(?:\s+\S+)?)\s+touch\b/i,
|
|
56865
|
+
"flow/automation execution traces"
|
|
56866
|
+
],
|
|
56867
|
+
[
|
|
56868
|
+
/\bhow\s+many\s+times\s+(?:has|have|did|was|were)\b[^.?!]{0,80}\b(?:executed|run|ran|fired|triggered)\b/i,
|
|
56869
|
+
"aggregate automation run counts (sfi.live_automation_fired infers per-record only)"
|
|
56870
|
+
],
|
|
56871
|
+
[
|
|
56872
|
+
/\b(?:the\s+)?last\s+time\b[^.?!]{0,60}\b(?:failed|errored|crashed|ran)\b/i,
|
|
56873
|
+
"run/failure forensics from past executions"
|
|
56874
|
+
],
|
|
56875
|
+
// Apex/query RUNTIME PROFILING + debug logs (hon-040/hon-044/hon-053):
|
|
56876
|
+
// CPU/heap profiles of a past run, debug logs, SOQL execution plans.
|
|
56877
|
+
// Static reads stay routed: "which classes RISK hitting governor limits"
|
|
56878
|
+
// (governor_limit_risks — no profile/log/plan noun), "which class contains
|
|
56879
|
+
// System.debug" (code-literal search — no `debug log` bigram).
|
|
56880
|
+
[
|
|
56881
|
+
/\b(?:cpu\s+time|heap\s+(?:usage|size)|memory\s+(?:usage|utilization|consumption))\b[^.?!]{0,50}\bprofile\b|\bprofile\b[^.?!]{0,50}\b(?:cpu\s+time|heap\s+usage)\b/i,
|
|
56882
|
+
"runtime CPU/heap profiling"
|
|
56883
|
+
],
|
|
56884
|
+
[/\bmemory\s+(?:utilization|consumption)\b|\bapplication\s+servers?\b/i, "infrastructure telemetry"],
|
|
56885
|
+
// Debug-LOG retrieval needs the retrieval-verb frame: "SHOW ME the debug
|
|
56886
|
+
// log from yesterday's batch run" is runtime; "leftover debug logs in apex"
|
|
56887
|
+
// (q522 — System.debug code artifacts, search_apex_source) and "is it in a
|
|
56888
|
+
// debug log anywhere" carry no retrieval verb and stay routed. Shield
|
|
56889
|
+
// EVENT-MONITORING logs (router-v2 R2, hon-057) are the same retrieval
|
|
56890
|
+
// shape — runtime telemetry no vault holds; "which flows subscribe to
|
|
56891
|
+
// platform events" carries no log noun and stays routed.
|
|
56892
|
+
[
|
|
56893
|
+
/\b(?:show|see|view|pull|get|give|retrieve|read|check|download|fetch)\b[^.?!]{0,45}\b(?:debug|event[\s-]monitoring)\s+logs?\b/i,
|
|
56894
|
+
"debug / event-monitoring logs"
|
|
56895
|
+
],
|
|
56896
|
+
[
|
|
56897
|
+
/\b(?:query|execution|soql)\s+plans?\b|\bindexes?\s+(?:are\s+)?being\s+used\b/i,
|
|
56898
|
+
"SOQL execution plans / index usage"
|
|
56899
|
+
],
|
|
56900
|
+
// Message DELIVERY telemetry (hon-056/hon-041): delivered/bounce counts and
|
|
56901
|
+
// the ACTUAL content of sent messages. Metadata reads stay routed: "which
|
|
56902
|
+
// email templates are unused" (live_email_template_usage), "what emails go
|
|
56903
|
+
// out for this approval" (sendsEmail edges — no delivered/sent-count ask).
|
|
56904
|
+
[
|
|
56905
|
+
/\b(?:emails?|sms|messages?|texts?)\b[^.?!]{0,50}\b(?:delivered|deliverability|bounced?|bounce\s+rate)\b/i,
|
|
56906
|
+
"message delivery telemetry"
|
|
56907
|
+
],
|
|
56908
|
+
[
|
|
56909
|
+
/\bhow\s+many\s+(?:emails?|sms|messages?|texts?)\b[^.?!]{0,60}\b(?:sent|delivered|went\s+out)\b/i,
|
|
56910
|
+
"sent-message counts"
|
|
56911
|
+
],
|
|
56912
|
+
[
|
|
56913
|
+
/\bactual\s+content\s+of\s+the\s+(?:emails?|messages?|texts?)\b/i,
|
|
56914
|
+
"the content of individually sent messages (the TEMPLATE body is metadata \u2014 sfi.get_component reads it)"
|
|
56915
|
+
],
|
|
56916
|
+
// Chatter/feed RUNTIME activity (q4450 "who posted last in the … chatter
|
|
56917
|
+
// group"): who posted, most recent post, feed content — CollaborationGroup
|
|
56918
|
+
// feed data that lives in the runtime org, never the vault. Metadata reads
|
|
56919
|
+
// stay routed: "which public groups exist" / "who is IN the group"
|
|
56920
|
+
// (live_group_members) carry no post/feed verb.
|
|
56921
|
+
[
|
|
56922
|
+
/\bwho\s+(?:posted|commented|last\s+posted)\b|\b(?:chatter|feed)\b[^.?!]{0,40}\b(?:posts?|activity|comments?)\b|\b(?:last|latest|recent)\s+(?:chatter|feed)\s+posts?\b/i,
|
|
56923
|
+
"Chatter/feed activity (who posted, most recent posts)"
|
|
56924
|
+
],
|
|
56925
|
+
// Site/community WEB ANALYTICS (hon-050): click paths, page views.
|
|
56926
|
+
[
|
|
56927
|
+
/\b(?:click|page|site|web)\s*[- ]?(?:analytics|traffic)\b|\bpage\s+views?\b|\bwhich\s+pages\b[^.?!]{0,60}\bvisit/i,
|
|
56928
|
+
"site/community click analytics"
|
|
56929
|
+
],
|
|
56930
|
+
// RECORD-LEVEL field history (hon-054): before/after values of changed
|
|
56931
|
+
// records. "Which fields HAVE history tracking enabled" is object metadata
|
|
56932
|
+
// and stays routed (no before/after-values bigram).
|
|
56933
|
+
[
|
|
56934
|
+
/\bbefore(?:\s*\/\s*|\s*-\s*|\s+and\s+)after\b[^.?!]{0,40}\b(?:values?|data)\b/i,
|
|
56935
|
+
"record-level field history (before/after values)"
|
|
56936
|
+
],
|
|
56937
|
+
// RECORD-ACCESS audit (q960/q1117): who ACCESSED/viewed a record or field.
|
|
56938
|
+
// "Who CAN see/access X" is a permissions read (who_can_access_object /
|
|
56939
|
+
// field_access_audit) and never matches — the arm requires the past-tense
|
|
56940
|
+
// accessed/viewed/opened event verb.
|
|
56941
|
+
[
|
|
56942
|
+
/\bwho\b[^.?!]{0,30}\b(?:accessed|viewed|opened|looked\s+at)\b[^.?!]{0,60}\b(?:records?|fields?|data)\b/i,
|
|
56943
|
+
"record-access audit events (who accessed what)"
|
|
56944
|
+
],
|
|
56945
|
+
// R3 §5b AUDIT-TRAIL-PLANE arms (8-question over-route cluster): Setup
|
|
56946
|
+
// Audit Trail asks — who changed a SETTING (FLS, sharing, OWD, session/
|
|
56947
|
+
// password policy) and when. Component lastModifiedBy IS metadata
|
|
56948
|
+
// (sfi.last_modified stays routed — those asks say "who changed <component>",
|
|
56949
|
+
// no settings noun); FLS/OWD/policy flips are only in the Setup Audit
|
|
56950
|
+
// Trail, which neither the vault nor the live plane reads.
|
|
56951
|
+
[/\bsetup\s+audit\s+trail\b/i, "Setup Audit Trail entries"],
|
|
56952
|
+
[
|
|
56953
|
+
/\bwho\s+(?:modified|changed|flipped|updated|edited)\b[^.?!]{0,50}\b(?:field-?level\s+security|fls|sharing\s+settings?|org-?wide\s+defaults?|owd|session\s+settings?|password\s+polic\w*|mfa)\b/i,
|
|
56954
|
+
"who changed org security settings and when (Setup Audit Trail data)"
|
|
56955
|
+
],
|
|
56956
|
+
// R3 §5b: report/dashboard DEFINITIONS (source object, columns, broken
|
|
56957
|
+
// references) are not retrieved into the vault — "which reports are
|
|
56958
|
+
// stale/unused" (live_report_usage) and "who can see the dashboard"
|
|
56959
|
+
// (folder access) carry none of these definition verbs and stay routed.
|
|
56960
|
+
[
|
|
56961
|
+
/\b(?:reports?|dashboards?)\b[^.?!]{0,50}\b(?:built|based)\s+on\b|\breports?\b[^.?!]{0,50}\breference\b[^.?!]{0,50}\b(?:deleted|renamed|broken|missing)\b|\bdashboards?\b[^.?!]{0,40}\b(?:broken|point\s+at)\b/i,
|
|
56962
|
+
"report/dashboard definitions (source object, columns, referenced fields) \u2014 not retrieved into the vault"
|
|
56963
|
+
]
|
|
55510
56964
|
];
|
|
55511
56965
|
RUNTIME_WINDOW = /\b(?:this\s+week|yesterday|last\s+night|in\s+the\s+\w+\s+incident)\b/i;
|
|
55512
56966
|
RUNTIME_INCIDENT = /\b(?:errors?|failed|failures?|incident|outage|gated)\b/i;
|
|
@@ -55520,6 +56974,8 @@ var init_refusal_gates = __esm({
|
|
|
55520
56974
|
METADATA_NOUN = /\b(?:field|object|flow|trigger|class|profile|permission|layout|validation|record\s+type|apex|report|dashboard|picklist|component|rule)\b|__(?:c|mdt|e|x|b|kav)\b/i;
|
|
55521
56975
|
DELIVERY_ASK = /\bemail\s+me\b|\bsend\s+(?:this|it)\s+to\b|\bpost\s+(?:this\s+|it\s+)?to\s+slack\b/i;
|
|
55522
56976
|
WRITE_CODE_ASK = /\bwrite\s+(?:me\s+)?(?:an?\s+)?(?:apex|lwc|trigger|class|component)\b[^.?!]*\b(?:that|to)\b/i;
|
|
56977
|
+
DRAFT_DOCUMENT_ASK = /\bdraft\s+(?:me\s+)?(?:a|an)\b[^.?!]{0,60}\b(?:agreement|contract|clause|policy|memo|press\s+release)\b/i;
|
|
56978
|
+
CAREER_ASK = /\bhow\s+do\s+i\s+become\b/i;
|
|
55523
56979
|
outOfScopeDisclosure = (topic) => `OUT OF SCOPE: sf-intelligence answers questions about this org's Salesforce metadata. ${topic} is outside that boundary (no external systems, no policy authorship, no code generation, no message delivery).`;
|
|
55524
56980
|
detectRefusalShape = (question) => {
|
|
55525
56981
|
const q2 = question.replace(/[‘’ʼ]/g, "'").replace(/\s+/g, " ").trim();
|
|
@@ -55530,7 +56986,9 @@ var init_refusal_gates = __esm({
|
|
|
55530
56986
|
return { kind: "injection-exfiltration", disclosure: INJECTION_DISCLOSURE };
|
|
55531
56987
|
}
|
|
55532
56988
|
if (!WRITE_EXCLUDER.test(q2)) {
|
|
55533
|
-
const
|
|
56989
|
+
const readFrame = WRITE_READ_FRAME.test(q2);
|
|
56990
|
+
const simulationFrame = SIMULATION_TAIL.test(q2) || readFrame;
|
|
56991
|
+
const verbPhrase = simulationFrame ? void 0 : (WRITE_SENTENCE_INITIAL.exec(q2) ?? WRITE_LEAD_IN.exec(q2) ?? WRITE_TRAILING_FRAME.exec(q2) ?? WRITE_MAKE_SCHEMA.exec(q2) ?? WRITE_GIVE_GRANT.exec(q2) ?? WRITE_GIVE_INITIAL.exec(q2) ?? WRITE_CHAINED_DUPE_DELETE.exec(q2) ?? WRITE_CHAINED_DEPLOY.exec(q2) ?? WRITE_CHECK_IN.exec(q2))?.[1]?.trim().replace(/[,;:]$/, "");
|
|
55534
56992
|
if (verbPhrase !== void 0 && verbPhrase.length > 0) {
|
|
55535
56993
|
const alternative = readOnlyAlternativeFor(verbPhrase, q2);
|
|
55536
56994
|
return {
|
|
@@ -55539,13 +56997,35 @@ var init_refusal_gates = __esm({
|
|
|
55539
56997
|
readOnlyAlternative: alternative
|
|
55540
56998
|
};
|
|
55541
56999
|
}
|
|
57000
|
+
const runPhrase = (RUN_IMPERATIVE_INITIAL.exec(q2) ?? RUN_IMPERATIVE_LEAD_IN.exec(q2) ?? RUN_ANAPHOR.exec(q2))?.[1]?.trim().replace(/[,;:]$/, "");
|
|
57001
|
+
if (runPhrase !== void 0 && runPhrase.length > 0) {
|
|
57002
|
+
const alternative = runReadOnlyAlternativeFor(runPhrase);
|
|
57003
|
+
return {
|
|
57004
|
+
kind: "write-imperative",
|
|
57005
|
+
disclosure: `REFUSED (read-only boundary): sf-intelligence never executes org automation \u2014 it cannot ${runPhrase}. I can show you what it WOULD do instead: ${alternative}.`,
|
|
57006
|
+
readOnlyAlternative: alternative
|
|
57007
|
+
};
|
|
57008
|
+
}
|
|
57009
|
+
}
|
|
57010
|
+
if ((IDENTITY_IDIOM.test(q2) || IDENTITY_ALLOWED_MUTATE.test(q2)) && !IDENTITY_LOOK_EXCLUDER.test(q2)) {
|
|
57011
|
+
return { kind: "identity-gap", disclosure: IDENTITY_DISCLOSURE };
|
|
57012
|
+
}
|
|
57013
|
+
if (!SIMULATION_TAIL.test(q2)) {
|
|
57014
|
+
const isForecast = FORECAST_VERB.test(q2) || FORECAST_TREND.test(q2) && FORECAST_HORIZON.test(q2) && FORECAST_OUTCOME.test(q2) || FORECAST_TREND.test(q2) && FORECAST_STRONG_OUTCOME.test(q2);
|
|
57015
|
+
if (isForecast) {
|
|
57016
|
+
return { kind: "forecast-gap", disclosure: FORECAST_DISCLOSURE };
|
|
57017
|
+
}
|
|
57018
|
+
}
|
|
57019
|
+
const provenanceCreate = PROVENANCE_CREATE.test(q2) || PROVENANCE_CREATE_DATE.test(q2) && /\bcreated?\b/i.test(q2);
|
|
57020
|
+
if (provenanceCreate && !(PROVENANCE_LASTMOD_ONLY.test(q2) && !PROVENANCE_CREATE.test(q2))) {
|
|
57021
|
+
return { kind: "provenance-gap", disclosure: PROVENANCE_DISCLOSURE };
|
|
55542
57022
|
}
|
|
55543
57023
|
const runtimeTopic = RUNTIME_TRIGGERS.find(([pattern]) => pattern.test(q2))?.[1] ?? (RUNTIME_WINDOW.test(q2) && RUNTIME_INCIDENT.test(q2) ? "runtime incident forensics" : void 0);
|
|
55544
57024
|
if (runtimeTopic !== void 0) {
|
|
55545
57025
|
return { kind: "runtime-analytics", disclosure: runtimeDisclosure(runtimeTopic) };
|
|
55546
57026
|
}
|
|
55547
57027
|
const externalSystem = EXTERNAL_SYSTEM.exec(q2)?.[1];
|
|
55548
|
-
const outOfScopeTopic = externalSystem !== void 0 ? `"${externalSystem}"` : RETENTION_POLICY_ASK.test(q2) ? "A data-retention policy (organizational governance, not org metadata)" : CONSENT_PROCESS_ASK.test(q2) ? "A consent process (organizational governance, not org metadata)" : POLICY_ASK.test(q2) ? "Org policy authorship" : OPINION_ASK.test(q2) ? "An opinion" : DELIVERY_ASK.test(q2) ? "Message delivery" : WRITE_CODE_ASK.test(q2) ? "Code generation" : SHOULD_WE.test(q2) && !METADATA_NOUN.test(q2) ? "An org-decision recommendation" : void 0;
|
|
57028
|
+
const outOfScopeTopic = externalSystem !== void 0 ? `"${externalSystem}"` : RETENTION_POLICY_ASK.test(q2) ? "A data-retention policy (organizational governance, not org metadata)" : CONSENT_PROCESS_ASK.test(q2) ? "A consent process (organizational governance, not org metadata)" : POLICY_ASK.test(q2) ? "Org policy authorship" : OPINION_ASK.test(q2) ? "An opinion" : DELIVERY_ASK.test(q2) ? "Message delivery" : WRITE_CODE_ASK.test(q2) ? "Code generation" : DRAFT_DOCUMENT_ASK.test(q2) ? "Document authorship (agreements, policies, memos)" : CAREER_ASK.test(q2) ? "Career/self-improvement guidance" : SHOULD_WE.test(q2) && !METADATA_NOUN.test(q2) ? "An org-decision recommendation" : void 0;
|
|
55549
57029
|
if (outOfScopeTopic !== void 0) {
|
|
55550
57030
|
return { kind: "out-of-scope", disclosure: outOfScopeDisclosure(outOfScopeTopic) };
|
|
55551
57031
|
}
|
|
@@ -55576,7 +57056,22 @@ var init_funnel_utterances = __esm({
|
|
|
55576
57056
|
"what validation rules exist on the Order object?",
|
|
55577
57057
|
"show me every component that references the <PermSet> permission set",
|
|
55578
57058
|
"is there an approval process on Opportunity?",
|
|
55579
|
-
"find me anything that blocks record creation when a required field is empty"
|
|
57059
|
+
"find me anything that blocks record creation when a required field is empty",
|
|
57060
|
+
// biz-user register (router-v2 R2): existence-check with a look-inside.
|
|
57061
|
+
"does a <Object> object exist in this org? I want to see its fields",
|
|
57062
|
+
// R3 custom-permission band (DIAGNOSIS-R3 §2 rank 2 — the #1 corpus
|
|
57063
|
+
// family): describe-what-it-gates and bypass/guard phrasings. The tools
|
|
57064
|
+
// work (0.1.19 CustomPermission exposure); the vocabulary was missing.
|
|
57065
|
+
"what does the <CustomPermission> custom permission bypass exactly?",
|
|
57066
|
+
"whos allowed to bypass the validation and what is that custom permission guarding?",
|
|
57067
|
+
"which custom permission gates the <FlowName> screen flow?",
|
|
57068
|
+
"is there a custom permission that could let someone bypass a control?",
|
|
57069
|
+
"which custom permissions have descriptions that mention bypassing something?",
|
|
57070
|
+
"tell me every custom permission checked by a validation rule",
|
|
57071
|
+
// R3 cross-object field-existence band (same §2 row).
|
|
57072
|
+
"does Amount__c exist on any object, and which ones?",
|
|
57073
|
+
"do we track a completion date anywhere I could report on?",
|
|
57074
|
+
"is the <RecordType> record type only on Case or is it on other objects too?"
|
|
55580
57075
|
],
|
|
55581
57076
|
"sfi.resolve": [
|
|
55582
57077
|
"pull the <vague-field-name> field \u2014 <PermSet> implies we have one",
|
|
@@ -55591,7 +57086,26 @@ var init_funnel_utterances = __esm({
|
|
|
55591
57086
|
"I keep hearing about a <fuzzy-name> flow \u2014 can you find it?",
|
|
55592
57087
|
"what's the canonical name for the field that stores the SLA deadline?",
|
|
55593
57088
|
"look up Status__c \u2014 which object is it on?",
|
|
55594
|
-
"do we have anything for tracking escalation dates on Case?"
|
|
57089
|
+
"do we have anything for tracking escalation dates on Case?",
|
|
57090
|
+
// biz-user register (router-v2 R2): nickname-existence and
|
|
57091
|
+
// component-type-confusion phrasings ("is that a flow or a trigger").
|
|
57092
|
+
"I keep calling it the '<nickname>' object \u2014 does this org have something like that?",
|
|
57093
|
+
"is <ComponentName> a flow or a trigger? I can't tell from the name",
|
|
57094
|
+
"what type of component is <ComponentName> \u2014 an Apex class or a flow?",
|
|
57095
|
+
// R3 existence/verification/typo band (DIAGNOSIS-R3 §2 rank 1 +
|
|
57096
|
+
// trap-122-a): explicit "does X exist" asks, prove-it challenges, and
|
|
57097
|
+
// is-that-the-right-name verification.
|
|
57098
|
+
"does <Field__c> exist on Contact or only on Lead?",
|
|
57099
|
+
"is <ComponentName> even the right name? prove it actually exists",
|
|
57100
|
+
"wait, is <Name__c> a field or an object?",
|
|
57101
|
+
"pull up <ComponentName> \u2014 I might have the name spelled wrong",
|
|
57102
|
+
"does the <ComponentName> component actually exist in this org, yes or no?",
|
|
57103
|
+
// R4 show-me corpus (DIAGNOSIS-R4 §2 lever 2): "show me / pull up / open X"
|
|
57104
|
+
// where X is a bare informal name that must be RESOLVED first — the display
|
|
57105
|
+
// verb over an unqualified nickname reaches resolve.
|
|
57106
|
+
"show me the <nickname> thing \u2014 I'm not sure of its exact name",
|
|
57107
|
+
"pull up whatever we call <nickname> around here",
|
|
57108
|
+
"open the one that sounds like <fuzzy-name>"
|
|
55595
57109
|
],
|
|
55596
57110
|
"sfi.capabilities": [
|
|
55597
57111
|
"what can you actually do?",
|
|
@@ -55648,7 +57162,12 @@ var init_funnel_utterances = __esm({
|
|
|
55648
57162
|
"pulse check \u2014 is this org healthy?",
|
|
55649
57163
|
"quick status: how is the org doing right now?",
|
|
55650
57164
|
"is there anything alarming about this org at a glance?",
|
|
55651
|
-
"one-liner on org health"
|
|
57165
|
+
"one-liner on org health",
|
|
57166
|
+
// biz-user register (router-v2 R2): "who is changing things" asked as a
|
|
57167
|
+
// people question, not a metadata question.
|
|
57168
|
+
"who has been modifying things in this org the most?",
|
|
57169
|
+
"who are the top contributors to changes in this org lately?",
|
|
57170
|
+
"who's been making the most changes to this org lately?"
|
|
55652
57171
|
],
|
|
55653
57172
|
"sfi.org_card": [
|
|
55654
57173
|
"show me the org's basic info \u2014 edition, limits, creation date",
|
|
@@ -55680,7 +57199,25 @@ var init_funnel_utterances = __esm({
|
|
|
55680
57199
|
"get me the raw component data for the Contact validation rule",
|
|
55681
57200
|
"fetch the permission set definition for <PermSet>",
|
|
55682
57201
|
"show me the layout metadata for Case",
|
|
55683
|
-
"pull the full metadata for <FlowName>"
|
|
57202
|
+
"pull the full metadata for <FlowName>",
|
|
57203
|
+
// R3 show-me-the-thing band (DIAGNOSIS-R3 §2 rank 4).
|
|
57204
|
+
"whats in the <PermSet> permission set?",
|
|
57205
|
+
"what is the <PermSet> permission set for? the name confuses me",
|
|
57206
|
+
"is the <FlowName> flow active or dormant?",
|
|
57207
|
+
"can you give me the raw metadata XML for the <CustomPermission> custom permission?",
|
|
57208
|
+
"pull up <ComponentName> so I can look at it",
|
|
57209
|
+
// R4 show-me corpus (DIAGNOSIS-R4 §2 lever 2): the "show me X / pull up X /
|
|
57210
|
+
// open X / bring up X / display X" families — display verbs over a single
|
|
57211
|
+
// NAMED component reach get_component. Generic vocabulary, no org tokens.
|
|
57212
|
+
"open the <FlowName> flow so I can see its definition",
|
|
57213
|
+
"bring up the <ApexClass> class for me",
|
|
57214
|
+
"display the metadata for the <PermSet> permission set",
|
|
57215
|
+
"let me see the <ComponentName> component in full",
|
|
57216
|
+
"open up <ComponentName> and show me everything about it",
|
|
57217
|
+
"can you show me what the <FlowName> flow contains?",
|
|
57218
|
+
"pull the definition of the <ComponentName> component",
|
|
57219
|
+
"I want to see the full <ApexClass> apex class source and metadata",
|
|
57220
|
+
"render the raw metadata for the <ComponentName> component"
|
|
55684
57221
|
],
|
|
55685
57222
|
"sfi.list_components": [
|
|
55686
57223
|
"whats on the Account object \u2014 give me the whole field list",
|
|
@@ -55695,7 +57232,25 @@ var init_funnel_utterances = __esm({
|
|
|
55695
57232
|
"show me all the triggers on Account",
|
|
55696
57233
|
"what approval processes are deployed?",
|
|
55697
57234
|
"list all custom labels",
|
|
55698
|
-
"give me every email template in the org"
|
|
57235
|
+
"give me every email template in the org",
|
|
57236
|
+
// R3 custom-permission enumeration band (DIAGNOSIS-R3 §2 rank 6) + the
|
|
57237
|
+
// "can i see the validation rules" type-noun phrasings.
|
|
57238
|
+
"which custom permissions exist and what do they gate?",
|
|
57239
|
+
"what custom permissions are defined in this org?",
|
|
57240
|
+
"pull the full custom permission inventory for the audit workpaper",
|
|
57241
|
+
"can i see the validation rules on the <Object> object",
|
|
57242
|
+
"is there validation on the <Object> object?",
|
|
57243
|
+
"show me all the invocable apex methods",
|
|
57244
|
+
// R4 show-me corpus (DIAGNOSIS-R4 §2 lever 2): "show me / pull up / open /
|
|
57245
|
+
// bring up ALL the X" enumeration phrasings over a component TYPE reach
|
|
57246
|
+
// list_components. Generic vocabulary, no org tokens.
|
|
57247
|
+
"open the list of every flow in this org",
|
|
57248
|
+
"bring up all the apex classes for me \u2014 the whole inventory",
|
|
57249
|
+
"display all the custom objects we have",
|
|
57250
|
+
"let me see everything of type permission set",
|
|
57251
|
+
"pull up the full list of validation rules",
|
|
57252
|
+
"show me the whole inventory of layouts",
|
|
57253
|
+
"bring up the entire list of triggers in the org"
|
|
55699
57254
|
],
|
|
55700
57255
|
"sfi.get_edges": [
|
|
55701
57256
|
"what are the edges between <ApexClass> and the objects it touches?",
|
|
@@ -55730,7 +57285,13 @@ var init_funnel_utterances = __esm({
|
|
|
55730
57285
|
"find flows that call the <ApexClass> Apex class",
|
|
55731
57286
|
"which flows have a Get Records step on the Account object?",
|
|
55732
57287
|
"search for flows that send emails",
|
|
55733
|
-
"find every flow that creates an Opportunity"
|
|
57288
|
+
"find every flow that creates an Opportunity",
|
|
57289
|
+
// R3 q738-class boundary recovery: custom-permission checks inside flow
|
|
57290
|
+
// XML, subflow-by-name, and field-stamping searches.
|
|
57291
|
+
"does any flow reference the <CustomPermission> custom permission to skip a validation rule?",
|
|
57292
|
+
"which custom permissions are checked inside flows?",
|
|
57293
|
+
"find the flow that fires the <SubflowName> subflow",
|
|
57294
|
+
"is there a flow that stamps <Field__c> when the record closes?"
|
|
55734
57295
|
],
|
|
55735
57296
|
"sfi.get_naming_convention_report": [
|
|
55736
57297
|
"do our field names follow any consistent pattern?",
|
|
@@ -55819,7 +57380,11 @@ var init_funnel_utterances = __esm({
|
|
|
55819
57380
|
"pull a random sample of Opportunity records",
|
|
55820
57381
|
"show me what Account data actually looks like",
|
|
55821
57382
|
"grab a handful of Order records for me to inspect",
|
|
55822
|
-
"give me a sample of 10 Opportunities with Stage = Prospecting"
|
|
57383
|
+
"give me a sample of 10 Opportunities with Stage = Prospecting",
|
|
57384
|
+
// biz-user register (router-v2 R2): single-record lookup by an id the
|
|
57385
|
+
// user has in hand, asking for every field value.
|
|
57386
|
+
"can you pull up the specific record for id <RecordId>? I need to see all their field values",
|
|
57387
|
+
"show me one actual record with every field value filled in"
|
|
55823
57388
|
],
|
|
55824
57389
|
"sfi.live_field_population": [
|
|
55825
57390
|
"what percentage of Contacts have the Email field filled in?",
|
|
@@ -55835,7 +57400,10 @@ var init_funnel_utterances = __esm({
|
|
|
55835
57400
|
"how many people are in the queue right now?",
|
|
55836
57401
|
"show me group sizes across all public groups",
|
|
55837
57402
|
"how many members does the Sales territory group have?",
|
|
55838
|
-
"how are Cases distributed by Status right now?"
|
|
57403
|
+
"how are Cases distributed by Status right now?",
|
|
57404
|
+
// biz-user register (router-v2 R2): distribution-by-field phrasing.
|
|
57405
|
+
"how are <Object> records distributed by their Type field?",
|
|
57406
|
+
"breakdown of records by picklist value \u2014 how many land in each bucket?"
|
|
55839
57407
|
],
|
|
55840
57408
|
"sfi.live_stale_records": [
|
|
55841
57409
|
"which Cases haven't been updated in over 90 days?",
|
|
@@ -55909,6 +57477,58 @@ var init_funnel_utterances = __esm({
|
|
|
55909
57477
|
"which license types do we have and how many are used?",
|
|
55910
57478
|
"how many Salesforce licenses are actually being used vs assigned?"
|
|
55911
57479
|
],
|
|
57480
|
+
// ENGINE-ARC §2a — assignment rosters (holders of a grantor). Vocabulary is
|
|
57481
|
+
// the HOLDER direction (who has / assigned to / roster / everyone with);
|
|
57482
|
+
// the GRANTS direction ("which permission sets grant edit on X") stays owned
|
|
57483
|
+
// by sfi.effective_permissions — do not add grants-phrasings here.
|
|
57484
|
+
"sfi.live_permset_holders": [
|
|
57485
|
+
"who has the <PermSet> permission set?",
|
|
57486
|
+
"list everyone assigned the <PermSet> permission set",
|
|
57487
|
+
"which users hold the <PermSetGroup> permission set group?",
|
|
57488
|
+
"show me the roster of users with the <PermSet> permission set",
|
|
57489
|
+
"how many users are assigned <PermSet> right now \u2014 directly or through a group?",
|
|
57490
|
+
"name-by-name list of users with the <Profile> profile",
|
|
57491
|
+
"which users are in the <PermSetGroup> permission set group?",
|
|
57492
|
+
"who holds <PermSet>, including via permission set groups?",
|
|
57493
|
+
"all users assigned the System Administrator profile",
|
|
57494
|
+
"holder count for the <PermSet> permission set \u2014 I need it for an audit"
|
|
57495
|
+
],
|
|
57496
|
+
// ENGINE-ARC §2c — the perm-set-less sweep (anti-join). Dormancy-only
|
|
57497
|
+
// phrasings ("who hasn't logged in") stay owned by sfi.live_inactive_users.
|
|
57498
|
+
"sfi.live_zombie_accounts": [
|
|
57499
|
+
"which active users have no permission sets assigned?",
|
|
57500
|
+
"find zombie accounts \u2014 login access but zero permission sets",
|
|
57501
|
+
"users who can log in but have no permission set assignments",
|
|
57502
|
+
"which users have nothing assigned beyond their profile?",
|
|
57503
|
+
"active accounts with zero permission set or group assignments",
|
|
57504
|
+
"audit accounts that can still log in but hold no perm sets"
|
|
57505
|
+
],
|
|
57506
|
+
// ENGINE-ARC §2b — runtime queue/public-group membership. "Which queues are
|
|
57507
|
+
// EMPTY" (hygiene sweep over declared metadata) stays owned by
|
|
57508
|
+
// sfi.empty_queues_and_groups; this family is the who's-in-it roster.
|
|
57509
|
+
"sfi.live_group_members": [
|
|
57510
|
+
"who's in the <QueueName> queue right now?",
|
|
57511
|
+
"list the members of the <GroupName> public group",
|
|
57512
|
+
"which users are in the Support queue?",
|
|
57513
|
+
"show me the current membership of the <QueueName> queue",
|
|
57514
|
+
"can the <QueueName> queue own Case records?",
|
|
57515
|
+
"what objects does the <QueueName> queue support?",
|
|
57516
|
+
"members of the <GroupName> group \u2014 including any nested groups or roles",
|
|
57517
|
+
"who is actually in this queue in the live org, not the vault snapshot?"
|
|
57518
|
+
],
|
|
57519
|
+
// ENGINE-ARC §3 — reverse direction: what a named USER holds. The GRANTS
|
|
57520
|
+
// direction ("what can <Profile> do", "expand the <PermSetGroup>") stays
|
|
57521
|
+
// owned by sfi.effective_permissions; this family is user → grantors.
|
|
57522
|
+
"sfi.live_user_permsets": [
|
|
57523
|
+
"what permission sets does <UserName> have?",
|
|
57524
|
+
"which permission sets is <UserName> assigned right now?",
|
|
57525
|
+
"list <UserName>'s permission set and permission set group assignments",
|
|
57526
|
+
"what does the user <UserName> hold \u2014 direct sets and via groups?",
|
|
57527
|
+
"which permission set groups is <UserName> a member of?",
|
|
57528
|
+
"show me every permission set assigned to <UserName>, with expirations",
|
|
57529
|
+
"does <UserName> have any expiring permission set assignments?",
|
|
57530
|
+
"what perm sets is jane.doe@example.com assigned in the live org?"
|
|
57531
|
+
],
|
|
55912
57532
|
"sfi.live_consent": [
|
|
55913
57533
|
"which Contacts have opted out of email?",
|
|
55914
57534
|
"show me consent status for marketing contacts",
|
|
@@ -55931,7 +57551,9 @@ var init_funnel_utterances = __esm({
|
|
|
55931
57551
|
"which users can see the Executive dashboards folder?",
|
|
55932
57552
|
"who can access the shared document library?",
|
|
55933
57553
|
"folder-level access for the Finance reports",
|
|
55934
|
-
"are any report or dashboard folders publicly accessible?"
|
|
57554
|
+
"are any report or dashboard folders publicly accessible?",
|
|
57555
|
+
// biz-user register (router-v2 R2): single-report ownership question.
|
|
57556
|
+
"the <ReportName> report \u2014 who owns it and who can run it?"
|
|
55935
57557
|
],
|
|
55936
57558
|
"sfi.live_email_template_usage": [
|
|
55937
57559
|
"which email templates are actually being used?",
|
|
@@ -55983,7 +57605,10 @@ var init_funnel_utterances = __esm({
|
|
|
55983
57605
|
"executive summary of org risk",
|
|
55984
57606
|
"what should I be worried about before we go live?",
|
|
55985
57607
|
"pull together the top risks I need to present to leadership",
|
|
55986
|
-
"is there anything that would block a production release right now?"
|
|
57608
|
+
"is there anything that would block a production release right now?",
|
|
57609
|
+
// biz-user register (router-v2 R2): quality asked as a grade.
|
|
57610
|
+
"is our Salesforce org well-architected? would you give it a passing grade?",
|
|
57611
|
+
"if an outside expert graded this org's setup, what would they flag?"
|
|
55987
57612
|
],
|
|
55988
57613
|
"sfi.field_cleanup_candidates": [
|
|
55989
57614
|
"which fields can we safely delete?",
|
|
@@ -56061,7 +57686,22 @@ var init_funnel_utterances = __esm({
|
|
|
56061
57686
|
"what's the net permission for a user with <Profile> and <PermSet>?",
|
|
56062
57687
|
"does this user actually have delete access on Account?",
|
|
56063
57688
|
"what objects can the <Profile> profile read?",
|
|
56064
|
-
"effective read/write/delete access for <Profile> on Order"
|
|
57689
|
+
"effective read/write/delete access for <Profile> on Order",
|
|
57690
|
+
// R3 PSG-expansion band (DIAGNOSIS-R3 permissions family): what a member
|
|
57691
|
+
// of a permission set GROUP ends up with — effective_permissions expands
|
|
57692
|
+
// PSGs (verified). "Which USERS are in the group / hold the set"
|
|
57693
|
+
// (permset-user-roster) is now answered LIVE by sfi.live_permset_holders
|
|
57694
|
+
// (ENGINE-ARC §2a) — that tool owns the holder-roster vocabulary.
|
|
57695
|
+
// permset-group-grants is a PARTIAL live flip (§4): live_permset_holders
|
|
57696
|
+
// surfaces PSG containment via PermissionSetGroupComponent, while the
|
|
57697
|
+
// 2-hop "which PSG grants custom permission X" chain stays an honest gap
|
|
57698
|
+
// note on that intent.
|
|
57699
|
+
"what does a user get through the <PermSetGroup> permission set group?",
|
|
57700
|
+
"expand the <PermSetGroup> permission set group \u2014 every permission a member ends up with",
|
|
57701
|
+
"if someone is assigned the <PermSetGroup> permission set group, do they automatically get <CustomPermission>?",
|
|
57702
|
+
"what custom permissions does a new hire end up with after all their permission set group assignments?",
|
|
57703
|
+
"can a user in the <PermSetGroup> permission set group edit these records?",
|
|
57704
|
+
"walk me through how permission set group inheritance works here, with a real group as the example"
|
|
56065
57705
|
],
|
|
56066
57706
|
"sfi.who_can_run": [
|
|
56067
57707
|
"who can run the <Report> report?",
|
|
@@ -56085,7 +57725,12 @@ var init_funnel_utterances = __esm({
|
|
|
56085
57725
|
"a user is getting access denied on Opportunity \u2014 what's wrong?",
|
|
56086
57726
|
"why is this record invisible to a specific user?",
|
|
56087
57727
|
"I need to figure out why a user can't open this record",
|
|
56088
|
-
"user says they get a 'No access' error on a Contact record \u2014 what gives?"
|
|
57728
|
+
"user says they get a 'No access' error on a Contact record \u2014 what gives?",
|
|
57729
|
+
// R3 access-diagnostics band (DIAGNOSIS-R3 permissions family): symptom
|
|
57730
|
+
// phrasings — greyed-out fields, blocked edits, blocked reassignment.
|
|
57731
|
+
"a user cant edit the <Field__c> field on a record, they say its greyed out \u2014 why?",
|
|
57732
|
+
"why cant a user with the <Profile> profile reassign the Case owner?",
|
|
57733
|
+
"a user reports the record is read-only for them \u2014 which layer blocks the edit?"
|
|
56089
57734
|
],
|
|
56090
57735
|
"sfi.layout_for_user": [
|
|
56091
57736
|
"what page layout does <Profile> see on Case?",
|
|
@@ -56101,7 +57746,11 @@ var init_funnel_utterances = __esm({
|
|
|
56101
57746
|
"what can a specific user actually do in the org?",
|
|
56102
57747
|
"check if <Username> can create Opportunities",
|
|
56103
57748
|
"what record types can this user access on Case?",
|
|
56104
|
-
"what flows can users with the <Profile> profile actually run?"
|
|
57749
|
+
"what flows can users with the <Profile> profile actually run?",
|
|
57750
|
+
// R3 access-diagnostics band: missing-button / failing-action symptoms.
|
|
57751
|
+
"a user says the Convert button is missing on a Lead \u2014 why?",
|
|
57752
|
+
"why does the send action fail for one user \u2014 do they need the <PermSet> permission set?",
|
|
57753
|
+
"which permission is this user missing to perform the action?"
|
|
56105
57754
|
],
|
|
56106
57755
|
"sfi.profile_security": [
|
|
56107
57756
|
"how locked down is the <Profile> profile?",
|
|
@@ -56141,7 +57790,9 @@ var init_funnel_utterances = __esm({
|
|
|
56141
57790
|
"which objects have tabs hidden from the support profile?",
|
|
56142
57791
|
"show me tab settings for the <Profile> profile",
|
|
56143
57792
|
"which tabs are set to 'Default Off' for a specific profile?",
|
|
56144
|
-
"which tabs are visible to users with the <Profile> profile?"
|
|
57793
|
+
"which tabs are visible to users with the <Profile> profile?",
|
|
57794
|
+
// biz-user register (router-v2 R2): imperative "list its tabs" shape.
|
|
57795
|
+
"the <Profile> profile \u2014 what does it have access to? list its tabs"
|
|
56145
57796
|
],
|
|
56146
57797
|
"sfi.lifecycle_process": [
|
|
56147
57798
|
"what's the Case lifecycle \u2014 statuses and transitions?",
|
|
@@ -56149,7 +57800,11 @@ var init_funnel_utterances = __esm({
|
|
|
56149
57800
|
"what are the valid Lead status transitions?",
|
|
56150
57801
|
"describe the record lifecycle for Order",
|
|
56151
57802
|
"what's the path an Account goes through from creation to active?",
|
|
56152
|
-
"what happens when an Opportunity moves to Closed Won?"
|
|
57803
|
+
"what happens when an Opportunity moves to Closed Won?",
|
|
57804
|
+
// biz-user register (router-v2 R2): status-transition asked as "what
|
|
57805
|
+
// kicks in when the value flips".
|
|
57806
|
+
"when the Status field flips to Resolved, what processes kick in for that value transition?",
|
|
57807
|
+
"what happens behind the scenes when a record moves to a new stage?"
|
|
56153
57808
|
],
|
|
56154
57809
|
"sfi.layout_assignments": [
|
|
56155
57810
|
"which layouts are assigned to which profiles on Case?",
|
|
@@ -56337,7 +57992,9 @@ var init_funnel_utterances = __esm({
|
|
|
56337
57992
|
"compare the Case validation rules in two orgs",
|
|
56338
57993
|
"what's different between the <Profile> profile in sandbox vs prod?",
|
|
56339
57994
|
"compare the <ApexClass> class across environments",
|
|
56340
|
-
"are these two record types actually different or just named differently?"
|
|
57995
|
+
"are these two record types actually different or just named differently?",
|
|
57996
|
+
// biz-user register (router-v2 R2): duplicate-automation confusion.
|
|
57997
|
+
"why do we have two triggers on the same object \u2014 what does each one do differently?"
|
|
56341
57998
|
],
|
|
56342
57999
|
"sfi.export_manifest": [
|
|
56343
58000
|
"export the full org manifest",
|
|
@@ -56369,7 +58026,9 @@ var init_funnel_utterances = __esm({
|
|
|
56369
58026
|
"who has delete access to Account?",
|
|
56370
58027
|
"object-level permission audit for Contact",
|
|
56371
58028
|
"show me which profiles have full CRUD on custom object <ObjectName>",
|
|
56372
|
-
"which profiles have Create access on the Case object?"
|
|
58029
|
+
"which profiles have Create access on the Case object?",
|
|
58030
|
+
// biz-user register (router-v2 R2): group-membership create-access ask.
|
|
58031
|
+
"can members of <GroupName> create <Object> records, and where does that access come from?"
|
|
56373
58032
|
],
|
|
56374
58033
|
"sfi.recordtype_availability": [
|
|
56375
58034
|
"which record types on Case are available to the Support profile?",
|
|
@@ -56391,7 +58050,10 @@ var init_funnel_utterances = __esm({
|
|
|
56391
58050
|
"describe this org to me like I just joined the team",
|
|
56392
58051
|
"how big is this org \u2014 fields, flows, classes, the works?",
|
|
56393
58052
|
"what's the overall complexity of this org?",
|
|
56394
|
-
"give me a lay of the land for this Salesforce environment"
|
|
58053
|
+
"give me a lay of the land for this Salesforce environment",
|
|
58054
|
+
// biz-user register (router-v2 R2): the new-joiner altitude idioms.
|
|
58055
|
+
"I just joined this team \u2014 what's the 10,000-foot view of what's in this Salesforce org?",
|
|
58056
|
+
"give me the bird's-eye view of this org for a newcomer"
|
|
56395
58057
|
],
|
|
56396
58058
|
"sfi.domain_clusters": [
|
|
56397
58059
|
"what functional areas does this org cover?",
|
|
@@ -56430,7 +58092,19 @@ var init_funnel_utterances = __esm({
|
|
|
56430
58092
|
"before I add a new flow to Contact, what else is already running on save?",
|
|
56431
58093
|
"which flows fire synchronously on Lead save?",
|
|
56432
58094
|
"complete save-event automation inventory for the Account object",
|
|
56433
|
-
"what happens behind the scenes when I create a new Contact?"
|
|
58095
|
+
"what happens behind the scenes when I create a new Contact?",
|
|
58096
|
+
// biz-user register (router-v2 R2): terse "show me its automation" shape.
|
|
58097
|
+
"the <Object> object \u2014 can you show me its automation?",
|
|
58098
|
+
// R3 save-cascade band (DIAGNOSIS-R3 §2 rank 7): terse "whats on save",
|
|
58099
|
+
// event-split phrasings, apex-on-insert, and the automation footprint.
|
|
58100
|
+
"whats on save for the <Object> object?",
|
|
58101
|
+
"how many automations fire when a record gets saved?",
|
|
58102
|
+
"what apex runs on Contact insert?",
|
|
58103
|
+
"list the flows that run on Contact insert",
|
|
58104
|
+
"which flows are triggered by Case creation vs Case update?",
|
|
58105
|
+
"whats the automation footprint on the <Object> object \u2014 flows, triggers, everything together?",
|
|
58106
|
+
"why did the flow not fire on my lead update \u2014 what is even wired to Lead saves?",
|
|
58107
|
+
"does anything on <Object> save update a date field automatically?"
|
|
56434
58108
|
],
|
|
56435
58109
|
"sfi.why_field_changed": [
|
|
56436
58110
|
"which flows write to Contact.Status__c?",
|
|
@@ -56467,7 +58141,28 @@ var init_funnel_utterances = __esm({
|
|
|
56467
58141
|
"what's the happy path through <FlowName>?",
|
|
56468
58142
|
"does <FlowName> have any error handling?",
|
|
56469
58143
|
"what variables does <FlowName> use?",
|
|
56470
|
-
"which object does <FlowName> primarily operate on?"
|
|
58144
|
+
"which object does <FlowName> primarily operate on?",
|
|
58145
|
+
// biz-user register (router-v2 R2): non-technical phrasings from the
|
|
58146
|
+
// business-user persona misses — purpose-of-flow asked as a business
|
|
58147
|
+
// question, and the "is that a flow or a trigger" type-confusion shape.
|
|
58148
|
+
"there's a flow called <FlowName> \u2014 what is it for? what business process does it support?",
|
|
58149
|
+
"what does the <FlowName> flow do for the business?",
|
|
58150
|
+
"<FlowName> \u2014 is that a flow or a trigger? what does it invoke?",
|
|
58151
|
+
"is <ComponentName> an Apex class or a flow? I want to see what it does",
|
|
58152
|
+
// R3 flow-explain band (DIAGNOSIS-R3 §2 rank 3): indirect explanation
|
|
58153
|
+
// phrasings — orchestrate/breakdown/what-triggers-it/fires-on-update.
|
|
58154
|
+
"before i touch the <FlowName> flow, what does it even do?",
|
|
58155
|
+
"what does the <FlowName> flow orchestrate?",
|
|
58156
|
+
"give me a breakdown of the <FlowName> flow",
|
|
58157
|
+
"what triggers the <FlowName> flow \u2014 a record change, a schedule, or a button?",
|
|
58158
|
+
"does <FlowName> fire on update too or just on insert?",
|
|
58159
|
+
"which record type does the <FlowName> flow assign to the Case?",
|
|
58160
|
+
"the <FlowName> screen flow \u2014 walk me through its screens and decisions",
|
|
58161
|
+
// R3 flow-diagnostics band ("why did it NOT fire / not stamp"):
|
|
58162
|
+
"why did the <FlowName> flow NOT fire when the record was updated?",
|
|
58163
|
+
"why is the <FlowName> flow not sending the confirmation email \u2014 walk the decision branches",
|
|
58164
|
+
"the flow didnt stamp the date when the stage moved \u2014 why not?",
|
|
58165
|
+
"a reviewer says the <FlowName> flow is skipping records \u2014 what does it actually gate?"
|
|
56471
58166
|
],
|
|
56472
58167
|
"sfi.explain_apex_method": [
|
|
56473
58168
|
"what does the <MethodName> method in <ApexClass> do?",
|
|
@@ -56475,7 +58170,12 @@ var init_funnel_utterances = __esm({
|
|
|
56475
58170
|
"walk me through the logic of <ApexClass>.<MethodName>",
|
|
56476
58171
|
"what does this method return and what does it modify?",
|
|
56477
58172
|
"explain the business logic in the <MethodName> method",
|
|
56478
|
-
"what does the AccountController class do?"
|
|
58173
|
+
"what does the AccountController class do?",
|
|
58174
|
+
// biz-user register (router-v2 R2): whole-class purpose questions and the
|
|
58175
|
+
// "Trigger in the name but is it a test class" type-confusion shape.
|
|
58176
|
+
"we have an Apex class called <ClassName> \u2014 what does it do and what objects does it touch?",
|
|
58177
|
+
"is <ClassName> an Apex test class or an actual trigger?",
|
|
58178
|
+
"that class has Trigger in its name \u2014 is it actually a test class?"
|
|
56479
58179
|
],
|
|
56480
58180
|
"sfi.explain_formula": [
|
|
56481
58181
|
"what does this formula field actually calculate?",
|
|
@@ -56499,7 +58199,10 @@ var init_funnel_utterances = __esm({
|
|
|
56499
58199
|
"which processes are candidates for Flow migration?",
|
|
56500
58200
|
"what Process Builder is left that we need to retire?",
|
|
56501
58201
|
"identify Process Builder processes that would be straightforward to convert",
|
|
56502
|
-
"which Process Builders should we migrate to Flow first?"
|
|
58202
|
+
"which Process Builders should we migrate to Flow first?",
|
|
58203
|
+
// biz-user register (router-v2 R2): "sunset/retire" business phrasing.
|
|
58204
|
+
"which automation is Salesforce going to sunset that we need to replace?",
|
|
58205
|
+
"what legacy automation is being retired that we still depend on?"
|
|
56503
58206
|
],
|
|
56504
58207
|
"sfi.unassigned_permission_sets": [
|
|
56505
58208
|
"are there permission sets that aren't assigned to anyone?",
|
|
@@ -56507,7 +58210,12 @@ var init_funnel_utterances = __esm({
|
|
|
56507
58210
|
"find permission sets that are effectively dead \u2014 no assignments",
|
|
56508
58211
|
"show me unassigned permission sets we could clean up",
|
|
56509
58212
|
"permission sets nobody has \u2014 are there any?",
|
|
56510
|
-
"which permission sets aren't assigned to any user?"
|
|
58213
|
+
"which permission sets aren't assigned to any user?",
|
|
58214
|
+
// biz-user register (router-v2 R2): single-perm-set assignment questions
|
|
58215
|
+
// ("is X assigned to anyone") — the premise-trap family phrasing.
|
|
58216
|
+
"is the <PermSet> permission set assigned to anyone?",
|
|
58217
|
+
"is <PermSet> assigned to any active users right now?",
|
|
58218
|
+
"who has the <PermSet> permission set assigned and what does it let them do?"
|
|
56511
58219
|
],
|
|
56512
58220
|
"sfi.empty_queues_and_groups": [
|
|
56513
58221
|
"are there any queues with no members?",
|
|
@@ -56523,7 +58231,9 @@ var init_funnel_utterances = __esm({
|
|
|
56523
58231
|
"give me a technical debt rating for this Salesforce org",
|
|
56524
58232
|
"debt score breakdown \u2014 what's contributing most?",
|
|
56525
58233
|
"quantify the technical debt in this org",
|
|
56526
|
-
"what's the overall technical debt score for this org?"
|
|
58234
|
+
"what's the overall technical debt score for this org?",
|
|
58235
|
+
// biz-user register (router-v2 R2): quality asked as a grade.
|
|
58236
|
+
"overall, is this org in good shape or carrying a lot of debt \u2014 passing grade or not?"
|
|
56527
58237
|
],
|
|
56528
58238
|
"sfi.code_quality_audit": [
|
|
56529
58239
|
"how good is our Apex code quality?",
|
|
@@ -56591,7 +58301,9 @@ var init_funnel_utterances = __esm({
|
|
|
56591
58301
|
"impact of making Contact.External_Id__c required",
|
|
56592
58302
|
"can I safely make this field required without breaking flows?",
|
|
56593
58303
|
"what automation would break if I make this field mandatory?",
|
|
56594
|
-
"what would break if I made Account.Website required?"
|
|
58304
|
+
"what would break if I made Account.Website required?",
|
|
58305
|
+
// biz-user register (router-v2 R2): "I want to set X to required" ask.
|
|
58306
|
+
"I want to set <Field__c> to required \u2014 can you check what flows and Apex would break?"
|
|
56595
58307
|
],
|
|
56596
58308
|
"sfi.what_if_deactivate_flow": [
|
|
56597
58309
|
"what breaks if I deactivate <FlowName>?",
|
|
@@ -56599,7 +58311,22 @@ var init_funnel_utterances = __esm({
|
|
|
56599
58311
|
"impact of deactivating the Case assignment flow",
|
|
56600
58312
|
"can I safely deactivate <FlowName> without breaking anything?",
|
|
56601
58313
|
"what would stop working if I switch off <FlowName>?",
|
|
56602
|
-
"what happens if I deactivate the <FlowName> flow?"
|
|
58314
|
+
"what happens if I deactivate the <FlowName> flow?",
|
|
58315
|
+
// biz-user register (router-v2 R2): "can you deactivate it safely" and
|
|
58316
|
+
// "impact if we turn it off" phrasings (premise-trap family).
|
|
58317
|
+
"can you deactivate <FlowName> safely? I need to know what depends on it",
|
|
58318
|
+
"we have a <ProcessName> process that runs on these records \u2014 what's the impact if we turn it off?",
|
|
58319
|
+
// R3 what-if band (DIAGNOSIS-R3 flows family + §5c simulation frames):
|
|
58320
|
+
// imperative-plus-impact phrasings the write gate now carves out.
|
|
58321
|
+
"deactivate <FlowName> and tell me the impact",
|
|
58322
|
+
"deactivating <FlowName> \u2014 consequences?",
|
|
58323
|
+
"what happens if the <FlowName> flow is turned off during a busy period?",
|
|
58324
|
+
"deactivate the <FlowName> flow \u2014 what happens to new records?",
|
|
58325
|
+
"deactivate <FlowName> and tell me what stops recalculating",
|
|
58326
|
+
"does deactivating <FlowName> stop the confirmation emails?",
|
|
58327
|
+
"when I deactivate <FlowName>, does the review process just stall?",
|
|
58328
|
+
"consequences of deactivating the <FlowName> flow?",
|
|
58329
|
+
"what would happen if the <FlowName> flow were deactivated?"
|
|
56603
58330
|
],
|
|
56604
58331
|
"sfi.what_if_disable_trigger": [
|
|
56605
58332
|
"what breaks if I disable the Contact trigger?",
|
|
@@ -56641,7 +58368,10 @@ var init_funnel_utterances = __esm({
|
|
|
56641
58368
|
"build a data dictionary I can share with the data team",
|
|
56642
58369
|
"export field descriptions and types for all custom objects",
|
|
56643
58370
|
"generate a schema reference document",
|
|
56644
|
-
"generate a data dictionary for the Contact object"
|
|
58371
|
+
"generate a data dictionary for the Contact object",
|
|
58372
|
+
// biz-user register (router-v2 R2): "reference sheet" business phrasing.
|
|
58373
|
+
"our business team needs a reference sheet for the <Object> object \u2014 field names, types, required vs optional",
|
|
58374
|
+
"make a field reference sheet the business team can read"
|
|
56645
58375
|
],
|
|
56646
58376
|
"sfi.generate_admin_handbook": [
|
|
56647
58377
|
"write an admin handbook for this org",
|
|
@@ -56657,7 +58387,9 @@ var init_funnel_utterances = __esm({
|
|
|
56657
58387
|
"create a high-level architecture summary for stakeholders",
|
|
56658
58388
|
"document the overall structure of this Salesforce implementation",
|
|
56659
58389
|
"generate an architecture diagram description for this org",
|
|
56660
|
-
"write an architecture overview document for this Salesforce org"
|
|
58390
|
+
"write an architecture overview document for this Salesforce org",
|
|
58391
|
+
// biz-user register (router-v2 R2): altitude idiom for leadership.
|
|
58392
|
+
"the leadership team wants the 10,000-foot architecture picture of our Salesforce org"
|
|
56661
58393
|
],
|
|
56662
58394
|
"sfi.generate_sharing_summary": [
|
|
56663
58395
|
"generate a sharing model summary for this org",
|
|
@@ -56698,12 +58430,18 @@ var init_funnel_utterances = __esm({
|
|
|
56698
58430
|
"subflow map \u2014 which flows call other flows?"
|
|
56699
58431
|
],
|
|
56700
58432
|
"sfi.downstream_effects": [
|
|
56701
|
-
"what does changing <Field__c> ripple out to?",
|
|
58433
|
+
"what does changing <Field__c> ripple out to downstream?",
|
|
56702
58434
|
"show me the downstream effects of modifying <ApexClass>",
|
|
56703
|
-
"if I change <FlowName>, what else is affected?",
|
|
58435
|
+
"if I change <FlowName>, what else is affected downstream?",
|
|
56704
58436
|
"downstream impact of updating the Case status picklist",
|
|
56705
58437
|
"what does it affect if <ComponentName> changes?",
|
|
56706
|
-
"what side effects does the AccountTrigger handler produce downstream?"
|
|
58438
|
+
"what side effects does the AccountTrigger handler produce downstream?",
|
|
58439
|
+
// self-recall reinforcement (router-v2 R2): the two paraphrase-only
|
|
58440
|
+
// utterances above ("what else is affected", "what does it affect")
|
|
58441
|
+
// shared no vocabulary with the tool's own name and lost the tie to
|
|
58442
|
+
// get_impact — anchor the downstream/effects terms.
|
|
58443
|
+
"what are the downstream effects if I change <FlowName>?",
|
|
58444
|
+
"show downstream effects of a change to <ComponentName>"
|
|
56707
58445
|
],
|
|
56708
58446
|
"sfi.test_coverage_for_method": [
|
|
56709
58447
|
"what tests cover the <MethodName> method?",
|
|
@@ -56783,7 +58521,12 @@ var init_funnel_utterances = __esm({
|
|
|
56783
58521
|
"what's the semantic meaning of Status__c on Order?",
|
|
56784
58522
|
"what does Amount__c represent \u2014 is it revenue, cost, or something else?",
|
|
56785
58523
|
"in plain English, what does this field track?",
|
|
56786
|
-
"what does the External_Id__c field on Account actually mean in this org?"
|
|
58524
|
+
"what does the External_Id__c field on Account actually mean in this org?",
|
|
58525
|
+
// biz-user register (router-v2 R2): label-first "what is it really for /
|
|
58526
|
+
// who sets it" phrasings from the business-user misses.
|
|
58527
|
+
"what is the <Field__c> field really for in this org?",
|
|
58528
|
+
"the bad email field \u2014 what does it represent and who sets it?",
|
|
58529
|
+
"what does that field mean in context \u2014 is it set by automation or by users, and what object is it on?"
|
|
56787
58530
|
],
|
|
56788
58531
|
"sfi.disambiguate_concepts": [
|
|
56789
58532
|
"we have both Priority__c and Case.Priority \u2014 which one should I use?",
|
|
@@ -56801,7 +58544,9 @@ var init_funnel_utterances = __esm({
|
|
|
56801
58544
|
"is Amount__c auto-populated or user-entered?",
|
|
56802
58545
|
"where does the data in this field come from?",
|
|
56803
58546
|
"is this field set by a trigger, a flow, or does a user type it in?",
|
|
56804
|
-
"trace the origin of values in <Field__c>"
|
|
58547
|
+
"trace the origin of values in <Field__c>",
|
|
58548
|
+
// biz-user register (router-v2 R2): "how does it get its value" shape.
|
|
58549
|
+
"how does the <Field__c> field get its value \u2014 is it a formula or is something writing to it?"
|
|
56805
58550
|
],
|
|
56806
58551
|
"sfi.find_field_anywhere": [
|
|
56807
58552
|
"find all objects that have a field called External_Id__c",
|
|
@@ -56985,7 +58730,28 @@ var init_funnel_utterances = __esm({
|
|
|
56985
58730
|
"which layouts include the <ComponentName> component?",
|
|
56986
58731
|
"which flows invoke <ComponentName>?",
|
|
56987
58732
|
"where is this custom label used?",
|
|
56988
|
-
"what uses the <PermSet> permission set \u2014 show me all assignments and references"
|
|
58733
|
+
"what uses the <PermSet> permission set \u2014 show me all assignments and references",
|
|
58734
|
+
// R3 reverse custom-permission grant band (DIAGNOSIS-R3 §2 rank 5): who
|
|
58735
|
+
// holds / which perm sets turn on / which profiles enable a custom
|
|
58736
|
+
// permission — the reverse lookup shipped in 0.1.19; the vocabulary here.
|
|
58737
|
+
"who holds the <CustomPermission> custom permission and which perm sets grant it?",
|
|
58738
|
+
"which permission sets or permission set groups turn on <CustomPermission>?",
|
|
58739
|
+
"which profiles enable the <CustomPermission> custom permission directly \u2014 not perm sets?",
|
|
58740
|
+
"is <CustomPermission> granted anywhere besides the <PermSet> permission set?",
|
|
58741
|
+
"i need the granting perm set for the <CustomPermission> custom permission",
|
|
58742
|
+
"how could a user have gotten the <CustomPermission> custom permission?",
|
|
58743
|
+
"what perm set gives access to the <ComponentName> component?",
|
|
58744
|
+
"who is allowed to bypass the validation \u2014 which custom permission guards it and who holds it?",
|
|
58745
|
+
// R4 custom-permission VERIFY-FIRST band (DIAGNOSIS-R4 lever 3, the 5 R3
|
|
58746
|
+
// residuals): a NAMED custom permission where the ask combines "what does
|
|
58747
|
+
// it control" WITH "who has / who grants it", or verifies a specific
|
|
58748
|
+
// granting perm set ("granted by X or a different one"). These name a
|
|
58749
|
+
// specific custom permission and want the reverse-grant answer.
|
|
58750
|
+
"the <CustomPermission> custom permission \u2014 what does it control and who has it?",
|
|
58751
|
+
"does the <CustomPermission> custom permission get granted by the <PermSet> perm set or a different one?",
|
|
58752
|
+
"which permission set enables the <CustomPermission> custom permission, and what does that permission gate?",
|
|
58753
|
+
"for the <CustomPermission> custom permission, tell me both the granting permission set and what it lets people do",
|
|
58754
|
+
"is <CustomPermission> actually granted by <PermSet>, or does the grant come from somewhere else?"
|
|
56989
58755
|
],
|
|
56990
58756
|
"sfi.installed_package_catalog": [
|
|
56991
58757
|
"what managed packages are installed in this org?",
|
|
@@ -57034,7 +58800,7 @@ var init_funnel_utterances = __esm({
|
|
|
57034
58800
|
});
|
|
57035
58801
|
|
|
57036
58802
|
// ../mcp/dist/src/semantic-funnel.js
|
|
57037
|
-
var STOPWORDS, SYNONYMS, PHRASE_SYNONYMS3, applyPhraseSynonyms3, tokenize6, TOOL_KEYWORDS, PLANE_OVERRIDES, planeForTool, EXCLUDED_FROM_CANDIDATES, liveRequiredForPlane, buildPlaneByTool, cachedPlanes, getPlaneByTool, resolveCandidatePlane, EXPANSION_WEIGHT, expandWeighted, buildToolDocs, cached, buildIndex, getFunnelIndex, CONF_HIGH_TOP1, CONF_HIGH_MARGIN, CONF_HIGH_COVERAGE, CONF_FLOOR_TOP1, CONF_MIN_COVERAGE, CONF_LOW_MARGIN, CONF_RESCUE_TOP1, CONF_RESCUE_COVERAGE, CONF_LOW_COVERAGE, funnelConfidence, semanticCandidates;
|
|
58803
|
+
var STOPWORDS, SYNONYMS, PHRASE_SYNONYMS3, applyPhraseSynonyms3, tokenize6, TOOL_KEYWORDS, PLANE_OVERRIDES, planeForTool, EXCLUDED_FROM_CANDIDATES, liveRequiredForPlane, buildPlaneByTool, cachedPlanes, getPlaneByTool, resolveCandidatePlane, EXPANSION_WEIGHT, expandWeighted, CORPUS_EXCLUDED, buildToolDocs, cached, buildIndex, getFunnelIndex, CONF_HIGH_TOP1, CONF_HIGH_MARGIN, CONF_HIGH_COVERAGE, CONF_FLOOR_TOP1, CONF_MIN_COVERAGE, CONF_LOW_MARGIN, CONF_RESCUE_TOP1, CONF_RESCUE_COVERAGE, CONF_LOW_COVERAGE, funnelConfidence, semanticCandidates;
|
|
57038
58804
|
var init_semantic_funnel = __esm({
|
|
57039
58805
|
"../mcp/dist/src/semantic-funnel.js"() {
|
|
57040
58806
|
"use strict";
|
|
@@ -57143,6 +58909,15 @@ var init_semantic_funnel = __esm({
|
|
|
57143
58909
|
many: ["count", "number"],
|
|
57144
58910
|
show: ["sample", "records"],
|
|
57145
58911
|
sample: ["records", "show"],
|
|
58912
|
+
// R4 show-me corpus (DIAGNOSIS-R4 §2 lever 2): display verbs → fetch/list the
|
|
58913
|
+
// component's metadata. EXPANSION_WEIGHT keeps originals dominant, so these
|
|
58914
|
+
// only tip a question that is ALREADY display-shaped ("open X", "pull up X",
|
|
58915
|
+
// "bring up X", "display X") toward get_component / list_components without
|
|
58916
|
+
// hijacking real intents.
|
|
58917
|
+
open: ["fetch", "component", "metadata", "definition"],
|
|
58918
|
+
display: ["fetch", "component", "metadata", "list"],
|
|
58919
|
+
bring: ["fetch", "component", "metadata", "list"],
|
|
58920
|
+
pull: ["fetch", "component", "metadata"],
|
|
57146
58921
|
// change / audit / freshness
|
|
57147
58922
|
changed: ["modified", "change", "history", "audit"],
|
|
57148
58923
|
change: ["modified", "history", "changed"],
|
|
@@ -57275,7 +59050,57 @@ var init_semantic_funnel = __esm({
|
|
|
57275
59050
|
untested: ["coverage", "gaps", "test", "tests"],
|
|
57276
59051
|
// queue / assignment routing family
|
|
57277
59052
|
queue: ["assignment", "routing", "owner", "group"],
|
|
57278
|
-
routed: ["assignment", "queue", "rule", "routing"]
|
|
59053
|
+
routed: ["assignment", "queue", "rule", "routing"],
|
|
59054
|
+
// ---- router-v2 R2 biz-user vocabulary bridges. Grown from the
|
|
59055
|
+
// business-user persona misses on the additions-tuning set; every entry is
|
|
59056
|
+
// GENERIC business English mapped to Salesforce vocabulary — no org terms.
|
|
59057
|
+
// "which automation gets sunset / retired" → legacy-migration vocabulary
|
|
59058
|
+
sunset: ["retire", "retired", "deprecated", "migrate", "legacy", "replace"],
|
|
59059
|
+
retiring: ["sunset", "deprecated", "migrate", "legacy"],
|
|
59060
|
+
// "when the status flips to X" → transition vocabulary
|
|
59061
|
+
flips: ["changes", "transition", "moves", "value", "status"],
|
|
59062
|
+
// "who are the top contributors" → change-activity vocabulary
|
|
59063
|
+
contributors: ["changes", "modified", "activity", "churn"],
|
|
59064
|
+
// "how are records distributed by X" → grouped-count vocabulary
|
|
59065
|
+
distributed: ["count", "group", "grouped", "breakdown"],
|
|
59066
|
+
// "would you give it a passing grade" → health/risk vocabulary
|
|
59067
|
+
grade: ["risk", "health", "score", "debt"],
|
|
59068
|
+
graded: ["risk", "health", "score", "debt"],
|
|
59069
|
+
architected: ["architecture", "risk", "health", "design"],
|
|
59070
|
+
// "the report my boss uses / the thing she made" → ownership vocabulary
|
|
59071
|
+
boss: ["owner", "user"],
|
|
59072
|
+
made: ["created", "built"],
|
|
59073
|
+
// ---- router-v2 R3 vocabulary bridges. Grown from the v0.1.22 labeled-miss
|
|
59074
|
+
// permissions/flows families (DIAGNOSIS-R3 §2); every entry is GENERIC
|
|
59075
|
+
// Salesforce/business vocabulary — no org terms — and safe at
|
|
59076
|
+
// EXPANSION_WEIGHT (originals always win).
|
|
59077
|
+
// custom-permission family: bypass / gate / guard / unlock verbs → the
|
|
59078
|
+
// permission vocabulary the tool docs actually use.
|
|
59079
|
+
bypass: ["permission", "custom", "validation", "skip"],
|
|
59080
|
+
bypasses: ["bypass", "permission", "custom", "validation"],
|
|
59081
|
+
bypassing: ["bypass", "permission", "custom", "validation"],
|
|
59082
|
+
gate: ["permission", "custom", "guard", "control"],
|
|
59083
|
+
gates: ["gate", "permission", "custom", "guard"],
|
|
59084
|
+
gated: ["gate", "permission", "custom"],
|
|
59085
|
+
guarding: ["gate", "permission", "custom", "validation"],
|
|
59086
|
+
unlock: ["grant", "permission", "access", "enable"],
|
|
59087
|
+
unlocks: ["grant", "permission", "access", "enable"],
|
|
59088
|
+
// PSG-expansion family: expand/inheritance → effective-permission union.
|
|
59089
|
+
expand: ["effective", "permission", "group", "union"],
|
|
59090
|
+
inheritance: ["group", "permission", "effective", "union"],
|
|
59091
|
+
inherits: ["group", "permission", "effective", "union"],
|
|
59092
|
+
// access-diagnostics family: greyed-out / read-only symptom words.
|
|
59093
|
+
greyed: ["edit", "editable", "access", "security", "field"],
|
|
59094
|
+
grayed: ["edit", "editable", "access", "security", "field"],
|
|
59095
|
+
// flow-diagnostics family: "didn't stamp the date", "what does it
|
|
59096
|
+
// orchestrate", "the flow is skipping records".
|
|
59097
|
+
stamp: ["writes", "sets", "populates", "updates", "flow"],
|
|
59098
|
+
stamps: ["writes", "sets", "populates", "updates", "flow"],
|
|
59099
|
+
orchestrate: ["flow", "subflow", "invoke", "explain"],
|
|
59100
|
+
orchestrates: ["flow", "subflow", "invoke", "explain"],
|
|
59101
|
+
skipping: ["skip", "bypass", "decision", "flow"],
|
|
59102
|
+
// automation-footprint family (what_happens_on_save / risk report).
|
|
59103
|
+
footprint: ["automation", "inventory", "overview", "save"]
|
|
57279
59104
|
};
|
|
57280
59105
|
PHRASE_SYNONYMS3 = [
|
|
57281
59106
|
["social security number", "ssn"],
|
|
@@ -57287,7 +59112,18 @@ var init_semantic_funnel = __esm({
|
|
|
57287
59112
|
// becomes 'break' (→ impact/dependency synonyms) + 'down' — the impact
|
|
57288
59113
|
// expansion hijacks the ranking. Collapse the phrase to the single token
|
|
57289
59114
|
// 'breakdown' (whose SYNONYMS entry bridges to explain/overview/walk).
|
|
57290
|
-
["break down", "breakdown"]
|
|
59115
|
+
["break down", "breakdown"],
|
|
59116
|
+
// router-v2 R2 biz-user idioms: altitude metaphors are OVERVIEW asks
|
|
59117
|
+
// ("10,000-foot view" survives tokenization as "...-foot view"), and
|
|
59118
|
+
// "kicks in" is the business phrasing of "fires".
|
|
59119
|
+
["foot view", "overview"],
|
|
59120
|
+
["birds eye view", "overview"],
|
|
59121
|
+
["bird's eye view", "overview"],
|
|
59122
|
+
["big picture", "overview"],
|
|
59123
|
+
["lay of the land", "overview"],
|
|
59124
|
+
["kicks in", "fires"],
|
|
59125
|
+
["kick in", "fire"],
|
|
59126
|
+
["reference sheet", "dictionary"]
|
|
57291
59127
|
].sort((a2, b2) => b2[0].length - a2[0].length);
|
|
57292
59128
|
applyPhraseSynonyms3 = (lower) => {
|
|
57293
59129
|
let out = lower;
|
|
@@ -57311,7 +59147,7 @@ var init_semantic_funnel = __esm({
|
|
|
57311
59147
|
return out;
|
|
57312
59148
|
};
|
|
57313
59149
|
TOOL_KEYWORDS = {
|
|
57314
|
-
"sfi.list_components": "list inventory enumerate catalog all what do we have how many exist objects fields custom fields how many fields on object contact account lead opportunity case metadata count layouts list views triggers validation rules record types web links flows classes triggers profiles permission sets layouts record types validation rules approval processes reports dashboards omniscripts custom standard relationship child parent inactive active picklist values which flows are inactive active draft obsolete flows triggers rules by status enumerate components",
|
|
59150
|
+
"sfi.list_components": "list inventory enumerate catalog all what do we have how many exist objects fields custom fields how many fields on object contact account lead opportunity case metadata count layouts list views triggers validation rules record types web links flows classes triggers profiles permission sets layouts record types validation rules approval processes reports dashboards omniscripts custom standard relationship child parent inactive active picklist values which flows are inactive active draft obsolete flows triggers rules by status enumerate components custom permissions defined exist inventory what do they gate",
|
|
57315
59151
|
"sfi.capabilities": "what can you do help capabilities what can i ask how do i use",
|
|
57316
59152
|
"sfi.automation_risk_report": "objects more than one multiple triggers per object trigger quality automation risk",
|
|
57317
59153
|
"sfi.live_group_count": "how many users assigned to profile membership group count",
|
|
@@ -57383,9 +59219,12 @@ var init_semantic_funnel = __esm({
|
|
|
57383
59219
|
}
|
|
57384
59220
|
return w2;
|
|
57385
59221
|
};
|
|
59222
|
+
CORPUS_EXCLUDED = /* @__PURE__ */ new Set(["sfi.route_question"]);
|
|
57386
59223
|
buildToolDocs = () => {
|
|
57387
59224
|
const docs = /* @__PURE__ */ new Map();
|
|
57388
59225
|
for (const tool of V01_TOOLS) {
|
|
59226
|
+
if (CORPUS_EXCLUDED.has(tool.name))
|
|
59227
|
+
continue;
|
|
57389
59228
|
const nameWords = tool.name.replace(/^sfi\./, "").replace(/_/g, " ");
|
|
57390
59229
|
const keywords = TOOL_KEYWORDS[tool.name] ?? "";
|
|
57391
59230
|
const utterances = (FUNNEL_UTTERANCES[tool.name] ?? []).join(" ");
|
|
@@ -57563,7 +59402,7 @@ import { createHash as createHash7 } from "node:crypto";
|
|
|
57563
59402
|
import { readFile as readFile82 } from "node:fs/promises";
|
|
57564
59403
|
import { join as join23, resolve as resolve3 } from "node:path";
|
|
57565
59404
|
import { z as z115 } from "zod";
|
|
57566
|
-
var RESOLVE_FALLBACK_MAX_TOKENS, SAVE_ORDER_INTENTS, SINGLE_ENTITY_INTENTS, COMPARISON_ASIDE, stripComparisonAside, tryResolveFallback, routeQuestionInputSchema, routeTrust, clarificationIdFor, selectedEntityArgsForRoute, extractEntityQuery, inferEntityTypes, normLiteral, refineEntityResolution, resolveParentQualifier, applyGlossaryAliases, entityAmbiguityRequiresClarification, hygienicClarificationOptions, splitCompoundQuestion, mixedInventoryAndStoragePlan, PLAN_FAMILY, ASSESSMENT_FAMILY, ROUTE_PREAMBLE_TOOLS, TOOL_COMPATIBLE_TYPES, ACCESS_FLAVORED_TOOLS, resolvedTypeForGuard, applyComponentTypeGuard, REGEX_BONUS, resolveActiveVaultAlias, buildRouteToolArgsMap, mergeRouteHintsIntoCandidates, invokeFromArgsMap, rerankForMode, MARGIN, FUNNEL_PRIMARY_MIN_SCORE, DESTRUCTIVE_TOOL, INFORMATIONAL_IMPACT_TOOL, planesDiverge, LIVE_DATA_SIGNAL, resolvePlaneTie, risksDiverge, marginClarification, LIVE_DISCLOSURE_LOOKAHEAD, liveConsentDisclosure, guidanceForMode, buildFunnelCandidates, routerMode, REFUSAL_INTENT, REFUSAL_GAP, refusalResponse, CONTEXT_SAVE_ORDER_TOOLS, CONTEXT_TOOL_ARG_KEYS, GENERIC_SCHEMA_INTENTS, contextArgsFor, buildContextContinuation, routeQuestionHandler;
|
|
59405
|
+
var RESOLVE_FALLBACK_MAX_TOKENS, SAVE_ORDER_INTENTS, SINGLE_ENTITY_INTENTS, COMPARISON_ASIDE, stripComparisonAside, tryResolveFallback, routeQuestionInputSchema, routeTrust, clarificationIdFor, selectedEntityArgsForRoute, isToolNameMention, extractEntityQuery, NAME_IGNORABLE_WORD, looksLikeComponentName, extractExistenceProbeToken, inferEntityTypes, normLiteral, refineEntityResolution, resolveParentQualifier, applyGlossaryAliases, questionAssertsNameMultiplicity, entityAmbiguityRequiresClarification, hygienicClarificationOptions, clarificationOptionIds, splitCompoundQuestion, mixedInventoryAndStoragePlan, PLAN_FAMILY, ASSESSMENT_FAMILY, ROUTE_PREAMBLE_TOOLS, SINGLE_COMPONENT_TARGET_TOOLS, routeTargetsSingleComponent, TOOL_COMPATIBLE_TYPES, ACCESS_FLAVORED_TOOLS, resolvedTypeForGuard, applyComponentTypeGuard, REGEX_BONUS, resolveActiveVaultAlias, buildRouteToolArgsMap, mergeRouteHintsIntoCandidates, invokeFromArgsMap, rerankForMode, MARGIN, FUNNEL_PRIMARY_MIN_SCORE, DESTRUCTIVE_TOOL, INFORMATIONAL_IMPACT_TOOL, planesDiverge, LIVE_DATA_SIGNAL, resolvePlaneTie, isCompoundPlaneAsk, risksDiverge, marginClarification, LIVE_DISCLOSURE_LOOKAHEAD, liveConsentDisclosure, guidanceForMode, buildFunnelCandidates, routerMode, REFUSAL_INTENT, REFUSAL_GAP, refusalResponse, CONTEXT_SAVE_ORDER_TOOLS, CONTEXT_TOOL_ARG_KEYS, GENERIC_SCHEMA_INTENTS, contextArgsFor, buildContextContinuation, routeQuestionHandler;
|
|
57567
59406
|
var init_route_question = __esm({
|
|
57568
59407
|
"../mcp/dist/src/tools/route-question.js"() {
|
|
57569
59408
|
"use strict";
|
|
@@ -57737,8 +59576,12 @@ var init_route_question = __esm({
|
|
|
57737
59576
|
}
|
|
57738
59577
|
return null;
|
|
57739
59578
|
};
|
|
59579
|
+
isToolNameMention = (token) => {
|
|
59580
|
+
const bare = token.toLowerCase().replace(/^sfi[._]/, "");
|
|
59581
|
+
return getPlaneByTool().has(`sfi.${bare}`);
|
|
59582
|
+
};
|
|
57740
59583
|
extractEntityQuery = (question, intent) => {
|
|
57741
|
-
const apiReference = question.
|
|
59584
|
+
const apiReference = [...question.matchAll(/\b[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)\b|\b[A-Za-z][A-Za-z0-9_]*__(?:c|mdt|e|x|b|kav)\b|\b[A-Za-z][A-Za-z0-9]*(?:_+[A-Za-z0-9]+)+\b/g)].map((match) => match[0]).find((token) => !isToolNameMention(token));
|
|
57742
59585
|
if (apiReference !== void 0) {
|
|
57743
59586
|
if (intent === "what-if-method-signature" && apiReference.includes(".")) {
|
|
57744
59587
|
return `${apiReference.slice(0, apiReference.indexOf("."))} class`;
|
|
@@ -57750,6 +59593,9 @@ var init_route_question = __esm({
|
|
|
57750
59593
|
}
|
|
57751
59594
|
return apiReference;
|
|
57752
59595
|
}
|
|
59596
|
+
const quotedTyped = question.match(/['"‘“]([A-Za-z][A-Za-z0-9_ ]{1,40}?)['"’”]\s+(?:field|object|profile|permission\s+set|flow|record\s+type)\b/i)?.[1];
|
|
59597
|
+
if (quotedTyped !== void 0)
|
|
59598
|
+
return quotedTyped.trim();
|
|
57753
59599
|
const prefixedTypePhrase = question.match(/\b((?:validation\s+rule|permission\s+set|record\s+type|page\s+layout|object|field|flow|class|trigger|layout|profile|report|dashboard)\s+(?:named\s+)?[A-Z][A-Za-z0-9_]*(?:\s+[A-Z][A-Za-z0-9_]*){0,5})\b/)?.[1];
|
|
57754
59600
|
if (prefixedTypePhrase !== void 0)
|
|
57755
59601
|
return prefixedTypePhrase.trim();
|
|
@@ -57767,6 +59613,15 @@ var init_route_question = __esm({
|
|
|
57767
59613
|
const parent = typedMatch?.[2];
|
|
57768
59614
|
return parent === void 0 ? phrase : `${phrase} on ${parent}`;
|
|
57769
59615
|
};
|
|
59616
|
+
NAME_IGNORABLE_WORD = /^(?:object|field|flow|class|trigger|page|layout|profile|permission|set|record|type|validation|rule|report|dashboard|logic|named|of|and|or|the|a|an|for)$/i;
|
|
59617
|
+
looksLikeComponentName = (query) => {
|
|
59618
|
+
const words = query.trim().split(/\s+/);
|
|
59619
|
+
if (words.length <= 1)
|
|
59620
|
+
return true;
|
|
59621
|
+
const nameWords = words.filter((word) => !NAME_IGNORABLE_WORD.test(word));
|
|
59622
|
+
return nameWords.length > 0 && nameWords.every((word) => /^["'‘“(]?[A-Z0-9]/.test(word));
|
|
59623
|
+
};
|
|
59624
|
+
extractExistenceProbeToken = (question) => [...question.matchAll(/\b[A-Za-z][A-Za-z0-9_]*__(?:c|mdt|e|x|b|kav)\b|\b[A-Za-z][A-Za-z0-9]*(?:_+[A-Za-z0-9]+)+\b/g)].map((match) => match[0]).find((token) => !isToolNameMention(token) && !/^\d/.test(token)) ?? null;
|
|
57770
59625
|
inferEntityTypes = (query, intent, question) => {
|
|
57771
59626
|
if (intent === "what-if-method-signature")
|
|
57772
59627
|
return ["ApexClass"];
|
|
@@ -57891,9 +59746,24 @@ var init_route_question = __esm({
|
|
|
57891
59746
|
]
|
|
57892
59747
|
};
|
|
57893
59748
|
};
|
|
57894
|
-
|
|
59749
|
+
questionAssertsNameMultiplicity = (question) => /\b(?:a\s+few|several|a\s+couple)\s+of\s+(?:these|those|them)\b|\b(?:two|three|four|five|\d+)\s+different\s+(?:things?|ones?|fields?|objects?|components?|versions?)\b|\bmore\s+than\s+one\s+(?:of\s+(?:these|those|them)|with\s+(?:that|this)\s+name)\b|\bwhich\s+one\s+(?:do|should|did)\s+i\b/i.test(question);
|
|
59750
|
+
entityAmbiguityRequiresClarification = (query, resolution, question, routeTargetsSingleComponentTool = false) => {
|
|
59751
|
+
if (resolution.disposition === "exact" && resolution.candidates.length >= 2 && !/__|\./.test(query)) {
|
|
59752
|
+
const [top2, second2] = resolution.candidates;
|
|
59753
|
+
const sameName = top2 !== void 0 && second2 !== void 0 && top2.matchKind === "exact" && second2.matchKind === "exact" && top2.base === second2.base && top2.apiName.toLowerCase() === second2.apiName.toLowerCase() && top2.parentApiName !== second2.parentApiName;
|
|
59754
|
+
if (sameName) {
|
|
59755
|
+
const promoted = resolveParentQualifier(question ?? "", {
|
|
59756
|
+
...resolution,
|
|
59757
|
+
disposition: "ambiguous"
|
|
59758
|
+
});
|
|
59759
|
+
if (promoted.disposition === "ambiguous")
|
|
59760
|
+
return true;
|
|
59761
|
+
}
|
|
59762
|
+
}
|
|
57895
59763
|
if (resolution.disposition !== "ambiguous" || resolution.candidates.length < 2)
|
|
57896
59764
|
return false;
|
|
59765
|
+
if (question !== void 0 && questionAssertsNameMultiplicity(question))
|
|
59766
|
+
return true;
|
|
57897
59767
|
const normalized = query.toLowerCase();
|
|
57898
59768
|
const literalMatches = resolution.candidates.filter((candidate) => candidate.apiName.toLowerCase() === normalized || candidate.id.toLowerCase().endsWith(`:${normalized}`));
|
|
57899
59769
|
if (literalMatches.length > 1)
|
|
@@ -57902,7 +59772,16 @@ var init_route_question = __esm({
|
|
|
57902
59772
|
return false;
|
|
57903
59773
|
const [top, second] = resolution.candidates;
|
|
57904
59774
|
const looksLikeNamedComponent = (candidate) => candidate.matchKind !== "fuzzy" && (candidate.nameCoverage ?? 1) >= 0.5;
|
|
57905
|
-
|
|
59775
|
+
if (top.base >= 0.92 && second.base >= 0.92 && second.score >= top.score * 0.97 && looksLikeNamedComponent(top) && looksLikeNamedComponent(second)) {
|
|
59776
|
+
return true;
|
|
59777
|
+
}
|
|
59778
|
+
if (routeTargetsSingleComponentTool) {
|
|
59779
|
+
const sameName = top.id !== second.id && top.apiName.toLowerCase() === second.apiName.toLowerCase();
|
|
59780
|
+
if (sameName && top.base >= 0.85 && second.base >= 0.85 && second.score >= top.score * 0.8) {
|
|
59781
|
+
return true;
|
|
59782
|
+
}
|
|
59783
|
+
}
|
|
59784
|
+
return false;
|
|
57906
59785
|
};
|
|
57907
59786
|
hygienicClarificationOptions = (candidates) => {
|
|
57908
59787
|
const top = candidates[0];
|
|
@@ -57911,6 +59790,21 @@ var init_route_question = __esm({
|
|
|
57911
59790
|
const kept = candidates.filter((candidate) => candidate.matchKind !== "fuzzy" && candidate.base >= top.base * 0.9);
|
|
57912
59791
|
return kept.length > 0 && kept[0] === top ? kept : [top, ...kept.filter((c2) => c2 !== top)];
|
|
57913
59792
|
};
|
|
59793
|
+
clarificationOptionIds = (candidates) => {
|
|
59794
|
+
const hygienic = hygienicClarificationOptions(candidates.map((candidate) => ({
|
|
59795
|
+
componentId: candidate.id,
|
|
59796
|
+
base: candidate.base,
|
|
59797
|
+
matchKind: candidate.matchKind
|
|
59798
|
+
}))).map((candidate) => candidate.componentId);
|
|
59799
|
+
if (hygienic.length >= 2)
|
|
59800
|
+
return hygienic;
|
|
59801
|
+
const top = candidates[0];
|
|
59802
|
+
if (top === void 0)
|
|
59803
|
+
return hygienic;
|
|
59804
|
+
const topName = top.apiName.toLowerCase();
|
|
59805
|
+
const sameName = candidates.filter((candidate) => candidate.apiName.toLowerCase() === topName).map((candidate) => candidate.id);
|
|
59806
|
+
return sameName.length >= 2 ? sameName : hygienic;
|
|
59807
|
+
};
|
|
57914
59808
|
splitCompoundQuestion = (question) => {
|
|
57915
59809
|
const parts = question.split(/(\?|;|\band\s+then\b|\bthen\b|\band\s+(?=who|what|which|how|where|when|why|is|are|can|should|does|do)\b)/i);
|
|
57916
59810
|
const clauses = [];
|
|
@@ -57959,6 +59853,34 @@ var init_route_question = __esm({
|
|
|
57959
59853
|
PLAN_FAMILY = /^sfi\.(what_if_|get_impact|safe_to_delete|downstream_effects|tests_for_change|field_lineage)/;
|
|
57960
59854
|
ASSESSMENT_FAMILY = /(_risk_report$|^sfi\.release_readiness|^sfi\.promotion_readiness|^sfi\.coverage_report|^sfi\.tech_debt_score|^sfi\.governor_limit_risks|^sfi\.crud_fls_audit)/;
|
|
57961
59855
|
ROUTE_PREAMBLE_TOOLS = /* @__PURE__ */ new Set(["sfi.resolve", "sfi.capabilities"]);
|
|
59856
|
+
SINGLE_COMPONENT_TARGET_TOOLS = /* @__PURE__ */ new Set([
|
|
59857
|
+
"sfi.explain_flow",
|
|
59858
|
+
"sfi.explain_apex_method",
|
|
59859
|
+
"sfi.explain_field",
|
|
59860
|
+
"sfi.explain_formula",
|
|
59861
|
+
"sfi.get_component",
|
|
59862
|
+
"sfi.downstream_effects",
|
|
59863
|
+
"sfi.component_history",
|
|
59864
|
+
"sfi.call_graph",
|
|
59865
|
+
"sfi.method_reachability",
|
|
59866
|
+
"sfi.safe_to_delete_field",
|
|
59867
|
+
"sfi.who_can_run",
|
|
59868
|
+
"sfi.field_360",
|
|
59869
|
+
"sfi.field_lineage",
|
|
59870
|
+
"sfi.what_happens_on_save",
|
|
59871
|
+
"sfi.order_of_execution",
|
|
59872
|
+
"sfi.field_access_audit",
|
|
59873
|
+
"sfi.object_access_audit",
|
|
59874
|
+
"sfi.who_can_access_object",
|
|
59875
|
+
"sfi.what_if_deactivate_flow",
|
|
59876
|
+
"sfi.what_if_disable_trigger"
|
|
59877
|
+
]);
|
|
59878
|
+
routeTargetsSingleComponent = (route) => {
|
|
59879
|
+
const primary = route.tools.find((tool) => !ROUTE_PREAMBLE_TOOLS.has(tool));
|
|
59880
|
+
if (primary === void 0)
|
|
59881
|
+
return false;
|
|
59882
|
+
return SINGLE_COMPONENT_TARGET_TOOLS.has(primary) || primary.startsWith("sfi.what_if_");
|
|
59883
|
+
};
|
|
57962
59884
|
TOOL_COMPATIBLE_TYPES = /* @__PURE__ */ new Map([
|
|
57963
59885
|
["sfi.call_graph", /* @__PURE__ */ new Set(["ApexClass", "ApexTrigger"])],
|
|
57964
59886
|
["sfi.method_reachability", /* @__PURE__ */ new Set(["ApexClass", "ApexTrigger"])],
|
|
@@ -58136,6 +60058,12 @@ var init_route_question = __esm({
|
|
|
58136
60058
|
return [...cands];
|
|
58137
60059
|
return [second, top, ...cands.slice(2)];
|
|
58138
60060
|
};
|
|
60061
|
+
isCompoundPlaneAsk = (question) => {
|
|
60062
|
+
const liveHalf = /\b(?:actually|really)\s+(?:running|firing|executing|live)\b|\bright\s+now\b|\bin\s+production\b/i;
|
|
60063
|
+
const vaultHalf = /\bwhat\s+(?:breaks?|depends|would\s+break|will\s+break)\b|\bwhat\s+does\b[^.?!]{0,60}\bdo\b|\bschema\b|\bmetadata\b|\bsafe\s+to\s+(?:disable|deactivate|delete|remove)\b|\btell\s+me\s+about\b/i;
|
|
60064
|
+
const conjoined = /\b(?:and|also|plus)\b/i;
|
|
60065
|
+
return liveHalf.test(question) && vaultHalf.test(question) && conjoined.test(question);
|
|
60066
|
+
};
|
|
58139
60067
|
risksDiverge = (a2, b2) => DESTRUCTIVE_TOOL.test(a2.tool) && INFORMATIONAL_IMPACT_TOOL.test(b2.tool) || DESTRUCTIVE_TOOL.test(b2.tool) && INFORMATIONAL_IMPACT_TOOL.test(a2.tool);
|
|
58140
60068
|
marginClarification = (cands, existingClarification) => {
|
|
58141
60069
|
if (existingClarification !== null)
|
|
@@ -58185,6 +60113,9 @@ var init_route_question = __esm({
|
|
|
58185
60113
|
REFUSAL_INTENT = {
|
|
58186
60114
|
"write-imperative": "refused-write",
|
|
58187
60115
|
"injection-exfiltration": "refused-injection",
|
|
60116
|
+
"identity-gap": "honest-gap-identity",
|
|
60117
|
+
"forecast-gap": "honest-gap-forecast",
|
|
60118
|
+
"provenance-gap": "honest-gap-provenance",
|
|
58188
60119
|
"runtime-analytics": "honest-gap-runtime",
|
|
58189
60120
|
"out-of-scope": "out-of-scope"
|
|
58190
60121
|
};
|
|
@@ -58197,6 +60128,18 @@ var init_route_question = __esm({
|
|
|
58197
60128
|
category: "injection",
|
|
58198
60129
|
note: "prompt-injection/exfiltration shape refused"
|
|
58199
60130
|
},
|
|
60131
|
+
"identity-gap": {
|
|
60132
|
+
category: "identity-gap",
|
|
60133
|
+
note: "first-person capability ask; no session-user identity \u2014 declined with a which-user clarify pointer"
|
|
60134
|
+
},
|
|
60135
|
+
"forecast-gap": {
|
|
60136
|
+
category: "forecast-gap",
|
|
60137
|
+
note: "future/forecast ask; no time-series or forecasting model \u2014 honest gap, current snapshot offered"
|
|
60138
|
+
},
|
|
60139
|
+
"provenance-gap": {
|
|
60140
|
+
category: "provenance-gap",
|
|
60141
|
+
note: "creator/authorship ask; vault has LastModified, never CreatedBy \u2014 honest gap disclosed"
|
|
60142
|
+
},
|
|
58200
60143
|
"runtime-analytics": {
|
|
58201
60144
|
category: "runtime-analytics",
|
|
58202
60145
|
note: "runtime/ops telemetry the product does not model; honest gap disclosed"
|
|
@@ -58470,8 +60413,11 @@ ${renderContextApplied(contextApplied2)}`
|
|
|
58470
60413
|
...entityTypes.length > 0 ? { types: entityTypes } : {}
|
|
58471
60414
|
}) : null;
|
|
58472
60415
|
const glossaryAwareEntityResolution = entityQuery !== null && entityResolution?.ok === true ? await applyGlossaryAliases(ctx, entityQuery, entityTypes, entityResolution.value) : null;
|
|
58473
|
-
|
|
58474
|
-
const entityClarificationRequired = entityQuery !== null && refinedEntityResolution !== null &&
|
|
60416
|
+
let refinedEntityResolution = contextExactResolution ?? (entityQuery !== null && glossaryAwareEntityResolution !== null ? resolveParentQualifier(input2.question, refineEntityResolution(entityQuery, glossaryAwareEntityResolution)) : null);
|
|
60417
|
+
const entityClarificationRequired = entityQuery !== null && refinedEntityResolution !== null && // component-type asks ("is X a flow or a trigger?") are EXEMPT: a
|
|
60418
|
+
// same-name cross-type collision is the ANSWER to that question, not an
|
|
60419
|
+
// obstacle — sfi.resolve enumerates the types and the host explains.
|
|
60420
|
+
route.intent !== "component-type" && entityAmbiguityRequiresClarification(entityQuery, refinedEntityResolution, input2.question, routeTargetsSingleComponent(route));
|
|
58475
60421
|
let entityEvidence = refinedEntityResolution !== null && refinedEntityResolution.disposition !== "none" ? {
|
|
58476
60422
|
query: entityQuery,
|
|
58477
60423
|
typeHints: entityTypes,
|
|
@@ -58497,9 +60443,10 @@ ${renderContextApplied(contextApplied2)}`
|
|
|
58497
60443
|
clarification: {
|
|
58498
60444
|
required: true,
|
|
58499
60445
|
question: "Several components match the named entity. Which component did you mean?",
|
|
58500
|
-
// P4 option hygiene
|
|
58501
|
-
//
|
|
58502
|
-
|
|
60446
|
+
// P4 option hygiene (fuzzy grazes / far-below-top rivals are junk),
|
|
60447
|
+
// with the R4 same-name fallback so a genuine same-name family whose
|
|
60448
|
+
// match reads `fuzzy` (the narrow-clarify shape) still offers ≥2 picks.
|
|
60449
|
+
options: clarificationOptionIds(refinedEntityResolution?.candidates ?? [])
|
|
58503
60450
|
}
|
|
58504
60451
|
};
|
|
58505
60452
|
} else if (entityEvidence?.disposition === "ambiguous" && route.clarification === null) {
|
|
@@ -58536,6 +60483,22 @@ ${renderContextApplied(contextApplied2)}`
|
|
|
58536
60483
|
route = { ...route, confidence: "low", clarification: marginClar };
|
|
58537
60484
|
}
|
|
58538
60485
|
}
|
|
60486
|
+
if (routerMode() === "hybrid" && route.clarification === null && isCompoundPlaneAsk(input2.question)) {
|
|
60487
|
+
const compoundCands = buildFunnelCandidates(route, input2.question, marginRouteToolArgs, input2.mode);
|
|
60488
|
+
const vaultTop = compoundCands.find((candidate) => candidate.plane === "vault");
|
|
60489
|
+
const liveTop = compoundCands.find((candidate) => candidate.plane === "live" || candidate.plane === "hybrid") ?? (/\b(?:running|firing|executing|fired?|ran)\b/i.test(input2.question) ? { tool: "sfi.live_automation_fired" } : void 0);
|
|
60490
|
+
if (vaultTop !== void 0 && liveTop !== void 0) {
|
|
60491
|
+
route = {
|
|
60492
|
+
...route,
|
|
60493
|
+
confidence: "low",
|
|
60494
|
+
clarification: {
|
|
60495
|
+
required: true,
|
|
60496
|
+
question: `This asks for BOTH a vault-metadata answer AND a live-org verification \u2014 \`${vaultTop.tool}\` answers from the offline vault; \`${liveTop.tool}\` queries the org right now and needs the opt-in live plane. Which should lead? (Pick one; the other can run as a follow-up.)`,
|
|
60497
|
+
options: [vaultTop.tool, liveTop.tool]
|
|
60498
|
+
}
|
|
60499
|
+
};
|
|
60500
|
+
}
|
|
60501
|
+
}
|
|
58539
60502
|
let premiseFlagged = false;
|
|
58540
60503
|
if (entityQuery !== null && refinedEntityResolution !== null && refinedEntityResolution.disposition === "none") {
|
|
58541
60504
|
const bareQuery = entityQuery.replace(/^(?:the|a|an)\s+/i, "").replace(/\s+(?:class(?:es)?|trigger(?:s)?|field(?:s)?|object(?:s)?|flow(?:s)?|component(?:s)?|layout(?:s)?|profile(?:s)?|report(?:s)?|dashboard(?:s)?|rule(?:s)?)$/i, "");
|
|
@@ -58544,7 +60507,43 @@ ${renderContextApplied(contextApplied2)}`
|
|
|
58544
60507
|
...entityTypes.length > 0 ? { types: entityTypes } : {}
|
|
58545
60508
|
}) : null;
|
|
58546
60509
|
const bareResolution = bareRetry?.ok === true && bareRetry.value.disposition !== "none" ? bareRetry.value : null;
|
|
58547
|
-
|
|
60510
|
+
let crossTypeResolution = null;
|
|
60511
|
+
if (bareResolution === null && entityTypes.length > 0) {
|
|
60512
|
+
const unscopedQuery = bareQuery.length > 0 ? bareQuery : entityQuery;
|
|
60513
|
+
const unscopedRetry = await resolveComponents(ctx.graph, unscopedQuery, { limit: 5 });
|
|
60514
|
+
if (unscopedRetry.ok && unscopedRetry.value.disposition !== "none" && (unscopedRetry.value.candidates[0]?.base ?? 0) >= 0.9) {
|
|
60515
|
+
crossTypeResolution = unscopedRetry.value;
|
|
60516
|
+
}
|
|
60517
|
+
}
|
|
60518
|
+
if (crossTypeResolution !== null) {
|
|
60519
|
+
const real = crossTypeResolution.candidates[0];
|
|
60520
|
+
const statedTypes = entityTypes.join("/");
|
|
60521
|
+
const typeWarning = `TYPE CHECK: no ${statedTypes} named '${entityQuery}' exists in the vault, but the name matches ${real.id} (a ${real.type}). The question's stated component type appears to be wrong \u2014 answer about the component that actually exists, and say so.`;
|
|
60522
|
+
route = {
|
|
60523
|
+
...route,
|
|
60524
|
+
confidence: "low",
|
|
60525
|
+
reason: `${route.reason} ${typeWarning}`
|
|
60526
|
+
};
|
|
60527
|
+
refinedEntityResolution = crossTypeResolution;
|
|
60528
|
+
entityEvidence = {
|
|
60529
|
+
query: entityQuery,
|
|
60530
|
+
typeHints: entityTypes,
|
|
60531
|
+
disposition: crossTypeResolution.disposition,
|
|
60532
|
+
clarificationRequired: false,
|
|
60533
|
+
warning: typeWarning,
|
|
60534
|
+
candidates: crossTypeResolution.candidates.slice(0, 5).map((candidate) => ({
|
|
60535
|
+
componentId: candidate.id,
|
|
60536
|
+
type: candidate.type,
|
|
60537
|
+
apiName: candidate.apiName,
|
|
60538
|
+
label: candidate.label,
|
|
60539
|
+
parentApiName: candidate.parentApiName,
|
|
60540
|
+
score: candidate.score,
|
|
60541
|
+
base: candidate.base,
|
|
60542
|
+
matchKind: candidate.matchKind,
|
|
60543
|
+
evidence: candidate.evidence
|
|
60544
|
+
}))
|
|
60545
|
+
};
|
|
60546
|
+
} else if (bareResolution !== null) {
|
|
58548
60547
|
entityEvidence = {
|
|
58549
60548
|
query: bareQuery,
|
|
58550
60549
|
typeHints: entityTypes,
|
|
@@ -58563,7 +60562,7 @@ ${renderContextApplied(contextApplied2)}`
|
|
|
58563
60562
|
evidence: candidate.evidence
|
|
58564
60563
|
}))
|
|
58565
60564
|
};
|
|
58566
|
-
} else {
|
|
60565
|
+
} else if (looksLikeComponentName(entityQuery)) {
|
|
58567
60566
|
premiseFlagged = true;
|
|
58568
60567
|
const premiseWarning = `PREMISE CHECK: no component matching '${entityQuery}' exists in the vault \u2014 verify the name (a typo, or metadata newer than the last refresh; /sfi-refresh may help). The routed tool fails closed on an unknown component; do not present its error as an answer about a real component.`;
|
|
58569
60568
|
route = {
|
|
@@ -58579,6 +60578,15 @@ ${renderContextApplied(contextApplied2)}`
|
|
|
58579
60578
|
warning: premiseWarning,
|
|
58580
60579
|
candidates: []
|
|
58581
60580
|
};
|
|
60581
|
+
} else {
|
|
60582
|
+
entityEvidence = {
|
|
60583
|
+
query: entityQuery,
|
|
60584
|
+
typeHints: entityTypes,
|
|
60585
|
+
disposition: "none",
|
|
60586
|
+
clarificationRequired: false,
|
|
60587
|
+
warning: null,
|
|
60588
|
+
candidates: []
|
|
60589
|
+
};
|
|
58582
60590
|
}
|
|
58583
60591
|
} else if (entityQuery !== null && refinedEntityResolution !== null && refinedEntityResolution.disposition === "ambiguous" && (/__(?:c|mdt|e|x|b|kav)\b/i.test(entityQuery) || entityQuery.includes(".")) && !refinedEntityResolution.candidates.some((candidate) => candidate.apiName.toLowerCase() === entityQuery.toLowerCase() || candidate.id.toLowerCase().endsWith(`:${entityQuery.toLowerCase()}`))) {
|
|
58584
60592
|
premiseFlagged = true;
|
|
@@ -58609,11 +60617,78 @@ ${renderContextApplied(contextApplied2)}`
|
|
|
58609
60617
|
candidates: []
|
|
58610
60618
|
};
|
|
58611
60619
|
}
|
|
60620
|
+
if (!premiseFlagged && entityQuery === null && route.clarification === null && route.intent !== "empty" && route.intent !== "component-lookup") {
|
|
60621
|
+
const probeToken = extractExistenceProbeToken(input2.question);
|
|
60622
|
+
if (probeToken !== null) {
|
|
60623
|
+
const probe = await resolveComponents(ctx.graph, probeToken, { limit: 5 });
|
|
60624
|
+
if (probe.ok) {
|
|
60625
|
+
const lowered = probeToken.toLowerCase();
|
|
60626
|
+
const loose = normLiteral(probeToken);
|
|
60627
|
+
const literalHit = probe.value.candidates.some((candidate) => candidate.apiName.toLowerCase() === lowered || candidate.id.toLowerCase().endsWith(`:${lowered}`) || normLiteral(candidate.apiName) === loose);
|
|
60628
|
+
if (probe.value.disposition === "none" || !literalHit) {
|
|
60629
|
+
premiseFlagged = true;
|
|
60630
|
+
const premiseWarning = `PREMISE CHECK: no component named '${probeToken}' exists in the vault \u2014 any listed candidates are fuzzy lookalikes, not the component you named. Verify the name (a typo, or metadata newer than the last refresh; /sfi-refresh may help). The routed tool fails closed on an unknown component; do not present its error as an answer about a real component.`;
|
|
60631
|
+
route = {
|
|
60632
|
+
...route,
|
|
60633
|
+
confidence: "low",
|
|
60634
|
+
reason: `${route.reason} ${premiseWarning}`
|
|
60635
|
+
};
|
|
60636
|
+
entityEvidence = {
|
|
60637
|
+
query: probeToken,
|
|
60638
|
+
typeHints: [],
|
|
60639
|
+
disposition: "none",
|
|
60640
|
+
clarificationRequired: false,
|
|
60641
|
+
warning: premiseWarning,
|
|
60642
|
+
candidates: probe.value.candidates.slice(0, 5).map((candidate) => ({
|
|
60643
|
+
componentId: candidate.id,
|
|
60644
|
+
type: candidate.type,
|
|
60645
|
+
apiName: candidate.apiName,
|
|
60646
|
+
label: candidate.label,
|
|
60647
|
+
parentApiName: candidate.parentApiName,
|
|
60648
|
+
score: candidate.score,
|
|
60649
|
+
base: candidate.base,
|
|
60650
|
+
matchKind: candidate.matchKind,
|
|
60651
|
+
evidence: candidate.evidence
|
|
60652
|
+
}))
|
|
60653
|
+
};
|
|
60654
|
+
}
|
|
60655
|
+
}
|
|
60656
|
+
}
|
|
60657
|
+
}
|
|
58612
60658
|
let contextRouteApplied = null;
|
|
58613
60659
|
if (validatedContext !== null && route.clarification === null && !premiseFlagged) {
|
|
58614
60660
|
const previous = validatedContext.previous;
|
|
58615
60661
|
const stillUnrouted = route.intent === "unrouted" && route.plane === "unknown";
|
|
58616
|
-
const
|
|
60662
|
+
const gapFollowUpFamily = stillUnrouted ? detectGapShapedFollowUp(input2.question) : null;
|
|
60663
|
+
if (gapFollowUpFamily !== null) {
|
|
60664
|
+
const gapDescription = {
|
|
60665
|
+
judgment: "a normative judgment (should/normal/risky) \u2014 sf-intelligence reads org metadata and has no opinion to offer",
|
|
60666
|
+
delivery: "file export or message delivery, which the product does not perform",
|
|
60667
|
+
"tool-self-capability": "the product's own capability boundary \u2014 sfi.capabilities answers what is and is not built",
|
|
60668
|
+
"deployment-status": "deployment/pending-change status, runtime telemetry the vault does not model",
|
|
60669
|
+
"runtime-analytics": "runtime org data (login history, API-call logs, approval-instance history, record field-history, subscription recipients, chatter posts, sandbox-refresh/deployment logs, or execution traces) \u2014 this is live runtime telemetry, not vault metadata"
|
|
60670
|
+
};
|
|
60671
|
+
const carried = previous.componentId !== void 0 ? ` The component carried from the previous turn (${previous.componentId}) was noted but the previous tool was NOT inherited \u2014 inheriting it would execute a read that cannot answer this ask.` : "";
|
|
60672
|
+
route = {
|
|
60673
|
+
question: input2.question,
|
|
60674
|
+
plane: "unknown",
|
|
60675
|
+
intent: "context-gap-followup",
|
|
60676
|
+
tools: [],
|
|
60677
|
+
liveRequired: false,
|
|
60678
|
+
needsResolve: false,
|
|
60679
|
+
reason: `HONEST GAP (context follow-up): this follow-up asks for ${gapDescription[gapFollowUpFamily] ?? gapFollowUpFamily}.${carried}`,
|
|
60680
|
+
gap: {
|
|
60681
|
+
category: "context-gap-followup",
|
|
60682
|
+
note: `gap-shaped follow-up (${gapFollowUpFamily}); context continuation suppressed rather than inheriting the previous tool`
|
|
60683
|
+
},
|
|
60684
|
+
confidence: "high",
|
|
60685
|
+
risk: "informational",
|
|
60686
|
+
alternatives: [],
|
|
60687
|
+
clarification: null,
|
|
60688
|
+
plan: []
|
|
60689
|
+
};
|
|
60690
|
+
}
|
|
60691
|
+
const reparamAnchor = gapFollowUpFamily !== null ? null : detectReparamAnchor(input2.question);
|
|
58617
60692
|
const ownRouteWeak = stillUnrouted || GENERIC_SCHEMA_INTENTS.has(route.intent) && route.confidence !== "high";
|
|
58618
60693
|
if (reparamAnchor !== null && ownRouteWeak && previous.tool !== void 0) {
|
|
58619
60694
|
let target = contextSubstitution === null && refinedEntityResolution?.disposition === "exact" ? refinedEntityResolution.candidates[0] : null;
|
|
@@ -58626,7 +60701,14 @@ ${renderContextApplied(contextApplied2)}`
|
|
|
58626
60701
|
const refinedTarget = refineEntityResolution(phrase, resolvedTarget.value);
|
|
58627
60702
|
if (refinedTarget.disposition === "exact") {
|
|
58628
60703
|
target = refinedTarget.candidates[0];
|
|
58629
|
-
} else if (refinedTarget.disposition === "ambiguous" && entityAmbiguityRequiresClarification(
|
|
60704
|
+
} else if (refinedTarget.disposition === "ambiguous" && entityAmbiguityRequiresClarification(
|
|
60705
|
+
phrase,
|
|
60706
|
+
refinedTarget,
|
|
60707
|
+
void 0,
|
|
60708
|
+
// The re-parameterization inherits previous.tool; the widened
|
|
60709
|
+
// R4 band applies when THAT tool is single-component-target.
|
|
60710
|
+
previous.tool !== void 0 && (SINGLE_COMPONENT_TARGET_TOOLS.has(previous.tool) || previous.tool.startsWith("sfi.what_if_"))
|
|
60711
|
+
)) {
|
|
58630
60712
|
route = {
|
|
58631
60713
|
...route,
|
|
58632
60714
|
confidence: "low",
|
|
@@ -58677,7 +60759,9 @@ ${renderContextApplied(contextApplied2)}`
|
|
|
58677
60759
|
}
|
|
58678
60760
|
}
|
|
58679
60761
|
let advisoryCandidates = null;
|
|
58680
|
-
|
|
60762
|
+
const questionHead = input2.question.trim().split(/\s+/).slice(0, 5).join(" ");
|
|
60763
|
+
const anaphorOnlyFragment = validatedContext === null && detectPronounAnchor(questionHead) !== null && (entityQuery === null || isAnaphorOnly(entityQuery));
|
|
60764
|
+
if (route.intent === "unrouted" && route.plane === "unknown" && route.clarification === null && !premiseFlagged && !anaphorOnlyFragment) {
|
|
58681
60765
|
const cands = buildFunnelCandidates(route, input2.question, marginRouteToolArgs, input2.mode);
|
|
58682
60766
|
advisoryCandidates = cands;
|
|
58683
60767
|
const resolvedExact = refinedEntityResolution?.disposition === "exact" ? refinedEntityResolution.candidates[0] : void 0;
|
|
@@ -67374,7 +69458,7 @@ __export(tools_exports, {
|
|
|
67374
69458
|
toolProfile: () => toolProfile
|
|
67375
69459
|
});
|
|
67376
69460
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
67377
|
-
var SEARCH_COMPONENTS_INPUT_SCHEMA, CAPABILITIES_INPUT_SCHEMA, SYNTHESIZE_ANSWER_INPUT_SCHEMA, ORG_PULSE_INPUT_SCHEMA, ORG_CARD_INPUT_SCHEMA, LIST_ANALYSES_INPUT_SCHEMA, DESCRIBE_ANALYSIS_INPUT_SCHEMA, RUN_ANALYSIS_INPUT_SCHEMA, FLEET_FIND_INPUT_SCHEMA, FLEET_DRIFT_RANKING_INPUT_SCHEMA, RESOLVE_INPUT_SCHEMA, GET_COMPONENT_INPUT_SCHEMA, GET_EDGES_INPUT_SCHEMA, LIST_COMPONENTS_INPUT_SCHEMA, GET_SUBGRAPH_INPUT_SCHEMA, SEARCH_APEX_SOURCE_INPUT_SCHEMA, SEARCH_FLOW_METADATA_INPUT_SCHEMA, NAMING_CONVENTION_REPORT_INPUT_SCHEMA, GET_MANIFEST_INPUT_SCHEMA, COVERAGE_REPORT_INPUT_SCHEMA, RETRIEVE_BLINDSPOT_REPORT_INPUT_SCHEMA, BASELINE_ACKNOWLEDGE_INPUT_SCHEMA, BASELINE_STATUS_INPUT_SCHEMA, TREND_INPUT_SCHEMA, CHURN_INPUT_SCHEMA, LIVE_ENABLED_PROPERTY, LIVE_DESCRIBE_INPUT_SCHEMA, LIVE_COUNT_INPUT_SCHEMA, LIVE_STALE_CHECK_INPUT_SCHEMA, LIVE_SAMPLE_INPUT_SCHEMA, LIVE_FIELD_POPULATION_INPUT_SCHEMA, LIVE_ORG_LIMITS_INPUT_SCHEMA, LIVE_PICKLIST_USAGE_INPUT_SCHEMA, LIVE_AUTOMATION_FIRED_INPUT_SCHEMA, LIVE_INACTIVE_USERS_INPUT_SCHEMA, LIVE_LICENSE_USAGE_INPUT_SCHEMA, LIVE_GROUP_COUNT_INPUT_SCHEMA, LIVE_STALE_RECORDS_INPUT_SCHEMA, LIVE_RECENT_ACTIVITY_INPUT_SCHEMA, LIVE_AGGREGATE_INPUT_SCHEMA, LIVE_DUPLICATE_CHECK_INPUT_SCHEMA, LIVE_OWNER_BREAKDOWN_INPUT_SCHEMA, LIVE_STORAGE_BY_OBJECT_INPUT_SCHEMA, LIVE_REPORT_USAGE_INPUT_SCHEMA, LIVE_FOLDER_ACCESS_INPUT_SCHEMA, LIVE_EMAIL_TEMPLATE_USAGE_INPUT_SCHEMA, LIVE_ORG_HEALTH_INPUT_SCHEMA, LIVE_CONSENT_INPUT_SCHEMA, ROUTE_QUESTION_INPUT_SCHEMA, SYNTHESIS_INPUT_SCHEMA, PERMISSION_RISK_REPORT_INPUT_SCHEMA, HEALTH_CHECK_INPUT_SCHEMA, GET_IMPACT_INPUT_SCHEMA, BLAST_RADIUS_LIVE_INPUT_SCHEMA, FIND_FORMULA_REFERENCES_INPUT_SCHEMA, FIND_APEX_USAGES_INPUT_SCHEMA, FIND_CODE_USAGES_INPUT_SCHEMA, EFFECTIVE_PERMISSIONS_INPUT_SCHEMA, LIST_VIEW_SHARING_INPUT_SCHEMA, WHO_CAN_RUN_INPUT_SCHEMA, WHO_CAN_ACCESS_OBJECT_INPUT_SCHEMA, WHY_CANT_USER_SEE_RECORD_INPUT_SCHEMA, LAYOUT_FOR_USER_INPUT_SCHEMA, LAYOUT_ASSIGNMENTS_INPUT_SCHEMA, USER_ABILITY_INPUT_SCHEMA, PROFILE_SECURITY_INPUT_SCHEMA, LIGHTNING_PAGES_INPUT_SCHEMA, APP_ACCESS_INPUT_SCHEMA, TAB_AVAILABILITY_INPUT_SCHEMA, LIFECYCLE_PROCESS_INPUT_SCHEMA, INTEGRATION_MAP_INPUT_SCHEMA, EVENT_SUBSCRIBERS_INPUT_SCHEMA, LOOKUP_RECORD_INPUT_SCHEMA, GUIDANCE_INPUT_SCHEMA, EXPLAIN_FIELD_INPUT_SCHEMA, SAFE_TO_DELETE_FIELD_INPUT_SCHEMA, UNUSED_COMPONENTS_INPUT_SCHEMA, DIFF_SNAPSHOTS_INPUT_SCHEMA, COMPARE_COMPONENTS_INPUT_SCHEMA, PII_INVENTORY_INPUT_SCHEMA, FIELD_ACCESS_AUDIT_INPUT_SCHEMA, OBJECT_ACCESS_AUDIT_INPUT_SCHEMA, RECORDTYPE_AVAILABILITY_INPUT_SCHEMA, FIELD_360_INPUT_SCHEMA, FIELD_LINEAGE_INPUT_SCHEMA, ORG_OVERVIEW_INPUT_SCHEMA, DOMAIN_CLUSTERS_INPUT_SCHEMA, CHANGED_SINCE_INPUT_SCHEMA, LAST_MODIFIED_INPUT_SCHEMA, WHAT_HAPPENS_ON_SAVE_INPUT_SCHEMA, WHY_FIELD_CHANGED_INPUT_SCHEMA, ORDER_OF_EXECUTION_INPUT_SCHEMA, EXPLAIN_FLOW_INPUT_SCHEMA, EXPLAIN_APEX_METHOD_INPUT_SCHEMA, EXPLAIN_FORMULA_INPUT_SCHEMA, EXPORT_MANIFEST_INPUT_SCHEMA, UNUSED_FIELDS_DEEP_INPUT_SCHEMA, FIELD_CLEANUP_CANDIDATES_INPUT_SCHEMA, PROCESS_BUILDER_MIGRATION_CANDIDATES_INPUT_SCHEMA, UNASSIGNED_PERMISSION_SETS_INPUT_SCHEMA, INSTALLED_PACKAGE_CATALOG_INPUT_SCHEMA, ANNOTATIONS_INPUT_SCHEMA, PROPOSE_ANNOTATION_INPUT_SCHEMA, COMPONENT_HISTORY_INPUT_SCHEMA, COMPONENT_AS_OF_INPUT_SCHEMA, FIND_COMPONENT_USAGES_INPUT_SCHEMA, EMPTY_QUEUES_AND_GROUPS_INPUT_SCHEMA, TECH_DEBT_SCORE_INPUT_SCHEMA, CODE_QUALITY_AUDIT_INPUT_SCHEMA, GOVERNOR_LIMIT_RISKS_INPUT_SCHEMA, FIND_HARDCODED_VALUES_INPUT_SCHEMA, CRUD_FLS_AUDIT_INPUT_SCHEMA, TEST_COVERAGE_GAPS_INPUT_SCHEMA, WHAT_IF_CHANGE_FIELD_VALUE_INPUT_SCHEMA, VALUE_CHANGE_AUDIT_INPUT_SCHEMA, WHAT_IF_CHANGE_FIELD_TYPE_INPUT_SCHEMA, WHAT_IF_REMOVE_PICKLIST_VALUE_INPUT_SCHEMA, WHAT_IF_MAKE_FIELD_REQUIRED_INPUT_SCHEMA, WHAT_IF_DEACTIVATE_FLOW_INPUT_SCHEMA, WHAT_IF_DISABLE_TRIGGER_INPUT_SCHEMA, WHAT_IF_CHANGE_METHOD_SIGNATURE_INPUT_SCHEMA, WHAT_IF_MERGE_PROFILES_INPUT_SCHEMA, WHAT_IF_SPLIT_PROFILE_INPUT_SCHEMA, GENERATE_DATA_DICTIONARY_INPUT_SCHEMA, GENERATE_ADMIN_HANDBOOK_INPUT_SCHEMA, GENERATE_ARCHITECTURE_OVERVIEW_INPUT_SCHEMA, GENERATE_SHARING_SUMMARY_INPUT_SCHEMA, GENERATE_COMPLIANCE_REPORT_INPUT_SCHEMA, GENERATE_ONBOARDING_DOC_INPUT_SCHEMA, CALL_GRAPH_INPUT_SCHEMA, DOWNSTREAM_EFFECTS_INPUT_SCHEMA, TEST_COVERAGE_FOR_METHOD_INPUT_SCHEMA, MEANINGFUL_TEST_AUDIT_INPUT_SCHEMA, METHOD_REACHABILITY_INPUT_SCHEMA, TESTS_FOR_CHANGE_INPUT_SCHEMA, PACKAGE_IMPACT_INPUT_SCHEMA, CDC_SUBSCRIBERS_INPUT_SCHEMA, ASYNC_CHAIN_DEPTH_INPUT_SCHEMA, SCHEDULED_JOB_CATALOG_INPUT_SCHEMA, OUTBOUND_MESSAGE_CATALOG_INPUT_SCHEMA, ENDPOINT_CATALOG_INPUT_SCHEMA, FIELD_MEANING_INPUT_SCHEMA, DISAMBIGUATE_CONCEPTS_INPUT_SCHEMA, FIELD_PROVENANCE_INPUT_SCHEMA, FIND_FIELD_ANYWHERE_INPUT_SCHEMA, FIND_SEMANTIC_FIELD_INPUT_SCHEMA, FIND_HARDCODED_VALUES_ANYWHERE_INPUT_SCHEMA, FIND_CLONE_PATTERNS_INPUT_SCHEMA, FIND_DEAD_CODE_INPUT_SCHEMA, CPQ_RULE_CHAIN_INPUT_SCHEMA, CPQ_QUOTE_TEMPLATE_BREAKDOWN_INPUT_SCHEMA, CPQ_DEPENDENCY_MAP_INPUT_SCHEMA, COMPARE_VAULTS_INPUT_SCHEMA, PROMOTION_READINESS_INPUT_SCHEMA, COMPARE_OBJECT_ACROSS_VAULTS_INPUT_SCHEMA, COMPARE_PROFILE_ACROSS_VAULTS_INPUT_SCHEMA, FIELD_MAPPING_BETWEEN_OBJECTS_INPUT_SCHEMA, INTEGRATION_PROCEDURE_CHAIN_INPUT_SCHEMA, OMNISCRIPT_FLOW_INPUT_SCHEMA, OMNIUICARD_WIDGET_BREAKDOWN_INPUT_SCHEMA, DATATRANSFORM_FIELD_MAP_INPUT_SCHEMA, DECISION_TABLE_BROWSE_INPUT_SCHEMA, FIND_DEPENDENCY_CYCLES_INPUT_SCHEMA, APEX_TEST_COVERAGE_INPUT_SCHEMA, AUTOMATION_BUILD_ADVISOR_INPUT_SCHEMA, APEX_BUILD_ADVISOR_INPUT_SCHEMA, FIELD_CHANGE_ADVISOR_INPUT_SCHEMA, LIVE_DRIFT_CHECK_INPUT_SCHEMA, ORG_HISTORY_INPUT_SCHEMA, WHAT_CHANGED_SINCE_REFRESH_INPUT_SCHEMA, V01_TOOLS, KNOWN_TOOL_NAMES, dispatchTool, runTool, advertisedTools, registerTools, MAX_RESPONSE_BYTES, RESPONSE_BUDGET_DEFAULT_BYTES, responseBudgetBytes, SLIM_STRING_THRESHOLD_BYTES2, SLIM_STRING_KEEP_CHARS2, TRUNCATE_KEEP_MIN, utf8Bytes2, truncateDataArrays, slimDataStrings, jsonResult, NARROWING_KNOB_RE, narrowingKnobs;
|
|
69461
|
+
var SEARCH_COMPONENTS_INPUT_SCHEMA, CAPABILITIES_INPUT_SCHEMA, SYNTHESIZE_ANSWER_INPUT_SCHEMA, ORG_PULSE_INPUT_SCHEMA, ORG_CARD_INPUT_SCHEMA, LIST_ANALYSES_INPUT_SCHEMA, DESCRIBE_ANALYSIS_INPUT_SCHEMA, RUN_ANALYSIS_INPUT_SCHEMA, FLEET_FIND_INPUT_SCHEMA, FLEET_DRIFT_RANKING_INPUT_SCHEMA, RESOLVE_INPUT_SCHEMA, GET_COMPONENT_INPUT_SCHEMA, GET_EDGES_INPUT_SCHEMA, LIST_COMPONENTS_INPUT_SCHEMA, GET_SUBGRAPH_INPUT_SCHEMA, SEARCH_APEX_SOURCE_INPUT_SCHEMA, SEARCH_FLOW_METADATA_INPUT_SCHEMA, NAMING_CONVENTION_REPORT_INPUT_SCHEMA, GET_MANIFEST_INPUT_SCHEMA, COVERAGE_REPORT_INPUT_SCHEMA, RETRIEVE_BLINDSPOT_REPORT_INPUT_SCHEMA, BASELINE_ACKNOWLEDGE_INPUT_SCHEMA, BASELINE_STATUS_INPUT_SCHEMA, TREND_INPUT_SCHEMA, CHURN_INPUT_SCHEMA, LIVE_ENABLED_PROPERTY, LIVE_DESCRIBE_INPUT_SCHEMA, LIVE_COUNT_INPUT_SCHEMA, LIVE_STALE_CHECK_INPUT_SCHEMA, LIVE_SAMPLE_INPUT_SCHEMA, LIVE_FIELD_POPULATION_INPUT_SCHEMA, LIVE_ORG_LIMITS_INPUT_SCHEMA, LIVE_PICKLIST_USAGE_INPUT_SCHEMA, LIVE_AUTOMATION_FIRED_INPUT_SCHEMA, LIVE_INACTIVE_USERS_INPUT_SCHEMA, LIVE_PERMSET_HOLDERS_INPUT_SCHEMA, LIVE_ZOMBIE_ACCOUNTS_INPUT_SCHEMA, LIVE_GROUP_MEMBERS_INPUT_SCHEMA, LIVE_USER_PERMSETS_INPUT_SCHEMA, LIVE_LICENSE_USAGE_INPUT_SCHEMA, LIVE_GROUP_COUNT_INPUT_SCHEMA, LIVE_STALE_RECORDS_INPUT_SCHEMA, LIVE_RECENT_ACTIVITY_INPUT_SCHEMA, LIVE_AGGREGATE_INPUT_SCHEMA, LIVE_DUPLICATE_CHECK_INPUT_SCHEMA, LIVE_OWNER_BREAKDOWN_INPUT_SCHEMA, LIVE_STORAGE_BY_OBJECT_INPUT_SCHEMA, LIVE_REPORT_USAGE_INPUT_SCHEMA, LIVE_FOLDER_ACCESS_INPUT_SCHEMA, LIVE_EMAIL_TEMPLATE_USAGE_INPUT_SCHEMA, LIVE_ORG_HEALTH_INPUT_SCHEMA, LIVE_CONSENT_INPUT_SCHEMA, ROUTE_QUESTION_INPUT_SCHEMA, SYNTHESIS_INPUT_SCHEMA, PERMISSION_RISK_REPORT_INPUT_SCHEMA, HEALTH_CHECK_INPUT_SCHEMA, GET_IMPACT_INPUT_SCHEMA, BLAST_RADIUS_LIVE_INPUT_SCHEMA, FIND_FORMULA_REFERENCES_INPUT_SCHEMA, FIND_APEX_USAGES_INPUT_SCHEMA, FIND_CODE_USAGES_INPUT_SCHEMA, EFFECTIVE_PERMISSIONS_INPUT_SCHEMA, LIST_VIEW_SHARING_INPUT_SCHEMA, WHO_CAN_RUN_INPUT_SCHEMA, WHO_CAN_ACCESS_OBJECT_INPUT_SCHEMA, WHY_CANT_USER_SEE_RECORD_INPUT_SCHEMA, LAYOUT_FOR_USER_INPUT_SCHEMA, LAYOUT_ASSIGNMENTS_INPUT_SCHEMA, USER_ABILITY_INPUT_SCHEMA, PROFILE_SECURITY_INPUT_SCHEMA, LIGHTNING_PAGES_INPUT_SCHEMA, APP_ACCESS_INPUT_SCHEMA, TAB_AVAILABILITY_INPUT_SCHEMA, LIFECYCLE_PROCESS_INPUT_SCHEMA, INTEGRATION_MAP_INPUT_SCHEMA, EVENT_SUBSCRIBERS_INPUT_SCHEMA, LOOKUP_RECORD_INPUT_SCHEMA, GUIDANCE_INPUT_SCHEMA, EXPLAIN_FIELD_INPUT_SCHEMA, SAFE_TO_DELETE_FIELD_INPUT_SCHEMA, UNUSED_COMPONENTS_INPUT_SCHEMA, DIFF_SNAPSHOTS_INPUT_SCHEMA, COMPARE_COMPONENTS_INPUT_SCHEMA, PII_INVENTORY_INPUT_SCHEMA, FIELD_ACCESS_AUDIT_INPUT_SCHEMA, OBJECT_ACCESS_AUDIT_INPUT_SCHEMA, RECORDTYPE_AVAILABILITY_INPUT_SCHEMA, FIELD_360_INPUT_SCHEMA, FIELD_LINEAGE_INPUT_SCHEMA, ORG_OVERVIEW_INPUT_SCHEMA, DOMAIN_CLUSTERS_INPUT_SCHEMA, CHANGED_SINCE_INPUT_SCHEMA, LAST_MODIFIED_INPUT_SCHEMA, WHAT_HAPPENS_ON_SAVE_INPUT_SCHEMA, WHY_FIELD_CHANGED_INPUT_SCHEMA, ORDER_OF_EXECUTION_INPUT_SCHEMA, EXPLAIN_FLOW_INPUT_SCHEMA, EXPLAIN_APEX_METHOD_INPUT_SCHEMA, EXPLAIN_FORMULA_INPUT_SCHEMA, EXPORT_MANIFEST_INPUT_SCHEMA, UNUSED_FIELDS_DEEP_INPUT_SCHEMA, FIELD_CLEANUP_CANDIDATES_INPUT_SCHEMA, PROCESS_BUILDER_MIGRATION_CANDIDATES_INPUT_SCHEMA, UNASSIGNED_PERMISSION_SETS_INPUT_SCHEMA, INSTALLED_PACKAGE_CATALOG_INPUT_SCHEMA, ANNOTATIONS_INPUT_SCHEMA, PROPOSE_ANNOTATION_INPUT_SCHEMA, COMPONENT_HISTORY_INPUT_SCHEMA, COMPONENT_AS_OF_INPUT_SCHEMA, FIND_COMPONENT_USAGES_INPUT_SCHEMA, EMPTY_QUEUES_AND_GROUPS_INPUT_SCHEMA, TECH_DEBT_SCORE_INPUT_SCHEMA, CODE_QUALITY_AUDIT_INPUT_SCHEMA, GOVERNOR_LIMIT_RISKS_INPUT_SCHEMA, FIND_HARDCODED_VALUES_INPUT_SCHEMA, CRUD_FLS_AUDIT_INPUT_SCHEMA, TEST_COVERAGE_GAPS_INPUT_SCHEMA, WHAT_IF_CHANGE_FIELD_VALUE_INPUT_SCHEMA, VALUE_CHANGE_AUDIT_INPUT_SCHEMA, WHAT_IF_CHANGE_FIELD_TYPE_INPUT_SCHEMA, WHAT_IF_REMOVE_PICKLIST_VALUE_INPUT_SCHEMA, WHAT_IF_MAKE_FIELD_REQUIRED_INPUT_SCHEMA, WHAT_IF_DEACTIVATE_FLOW_INPUT_SCHEMA, WHAT_IF_DISABLE_TRIGGER_INPUT_SCHEMA, WHAT_IF_CHANGE_METHOD_SIGNATURE_INPUT_SCHEMA, WHAT_IF_MERGE_PROFILES_INPUT_SCHEMA, WHAT_IF_SPLIT_PROFILE_INPUT_SCHEMA, GENERATE_DATA_DICTIONARY_INPUT_SCHEMA, GENERATE_ADMIN_HANDBOOK_INPUT_SCHEMA, GENERATE_ARCHITECTURE_OVERVIEW_INPUT_SCHEMA, GENERATE_SHARING_SUMMARY_INPUT_SCHEMA, GENERATE_COMPLIANCE_REPORT_INPUT_SCHEMA, GENERATE_ONBOARDING_DOC_INPUT_SCHEMA, CALL_GRAPH_INPUT_SCHEMA, DOWNSTREAM_EFFECTS_INPUT_SCHEMA, TEST_COVERAGE_FOR_METHOD_INPUT_SCHEMA, MEANINGFUL_TEST_AUDIT_INPUT_SCHEMA, METHOD_REACHABILITY_INPUT_SCHEMA, TESTS_FOR_CHANGE_INPUT_SCHEMA, PACKAGE_IMPACT_INPUT_SCHEMA, CDC_SUBSCRIBERS_INPUT_SCHEMA, ASYNC_CHAIN_DEPTH_INPUT_SCHEMA, SCHEDULED_JOB_CATALOG_INPUT_SCHEMA, OUTBOUND_MESSAGE_CATALOG_INPUT_SCHEMA, ENDPOINT_CATALOG_INPUT_SCHEMA, FIELD_MEANING_INPUT_SCHEMA, DISAMBIGUATE_CONCEPTS_INPUT_SCHEMA, FIELD_PROVENANCE_INPUT_SCHEMA, FIND_FIELD_ANYWHERE_INPUT_SCHEMA, FIND_SEMANTIC_FIELD_INPUT_SCHEMA, FIND_HARDCODED_VALUES_ANYWHERE_INPUT_SCHEMA, FIND_CLONE_PATTERNS_INPUT_SCHEMA, FIND_DEAD_CODE_INPUT_SCHEMA, CPQ_RULE_CHAIN_INPUT_SCHEMA, CPQ_QUOTE_TEMPLATE_BREAKDOWN_INPUT_SCHEMA, CPQ_DEPENDENCY_MAP_INPUT_SCHEMA, COMPARE_VAULTS_INPUT_SCHEMA, PROMOTION_READINESS_INPUT_SCHEMA, COMPARE_OBJECT_ACROSS_VAULTS_INPUT_SCHEMA, COMPARE_PROFILE_ACROSS_VAULTS_INPUT_SCHEMA, FIELD_MAPPING_BETWEEN_OBJECTS_INPUT_SCHEMA, INTEGRATION_PROCEDURE_CHAIN_INPUT_SCHEMA, OMNISCRIPT_FLOW_INPUT_SCHEMA, OMNIUICARD_WIDGET_BREAKDOWN_INPUT_SCHEMA, DATATRANSFORM_FIELD_MAP_INPUT_SCHEMA, DECISION_TABLE_BROWSE_INPUT_SCHEMA, FIND_DEPENDENCY_CYCLES_INPUT_SCHEMA, APEX_TEST_COVERAGE_INPUT_SCHEMA, AUTOMATION_BUILD_ADVISOR_INPUT_SCHEMA, APEX_BUILD_ADVISOR_INPUT_SCHEMA, FIELD_CHANGE_ADVISOR_INPUT_SCHEMA, LIVE_DRIFT_CHECK_INPUT_SCHEMA, ORG_HISTORY_INPUT_SCHEMA, WHAT_CHANGED_SINCE_REFRESH_INPUT_SCHEMA, V01_TOOLS, KNOWN_TOOL_NAMES, dispatchTool, runTool, advertisedTools, registerTools, MAX_RESPONSE_BYTES, RESPONSE_BUDGET_DEFAULT_BYTES, responseBudgetBytes, SLIM_STRING_THRESHOLD_BYTES2, SLIM_STRING_KEEP_CHARS2, TRUNCATE_KEEP_MIN, utf8Bytes2, truncateDataArrays, slimDataStrings, jsonResult, NARROWING_KNOB_RE, narrowingKnobs;
|
|
67378
69462
|
var init_tools = __esm({
|
|
67379
69463
|
"../mcp/dist/src/tools/index.js"() {
|
|
67380
69464
|
"use strict";
|
|
@@ -67913,6 +69997,83 @@ var init_tools = __esm({
|
|
|
67913
69997
|
...LIVE_ENABLED_PROPERTY
|
|
67914
69998
|
}
|
|
67915
69999
|
});
|
|
70000
|
+
LIVE_PERMSET_HOLDERS_INPUT_SCHEMA = Object.freeze({
|
|
70001
|
+
type: "object",
|
|
70002
|
+
required: ["name"],
|
|
70003
|
+
properties: {
|
|
70004
|
+
name: {
|
|
70005
|
+
type: "string",
|
|
70006
|
+
minLength: 1,
|
|
70007
|
+
description: "Exact PermissionSet.Name / PermissionSetGroup DeveloperName / Profile.Name (labels accepted)."
|
|
70008
|
+
},
|
|
70009
|
+
kind: {
|
|
70010
|
+
type: "string",
|
|
70011
|
+
enum: ["permissionSet", "permissionSetGroup", "profile", "auto"],
|
|
70012
|
+
description: "What `name` names. Default 'auto' probes PermissionSet \u2192 PermissionSetGroup \u2192 Profile by exact name/label; no-match or ambiguity errors honestly."
|
|
70013
|
+
},
|
|
70014
|
+
includeInactiveAssignees: { type: "boolean" },
|
|
70015
|
+
includeViaGroups: {
|
|
70016
|
+
type: "boolean",
|
|
70017
|
+
description: "For kind permissionSet: also count holders receiving the set through a PermissionSetGroup containing it (default true)."
|
|
70018
|
+
},
|
|
70019
|
+
groupBy: { type: "string", enum: ["none", "profile"] },
|
|
70020
|
+
limit: { type: "integer", minimum: 1, maximum: 500 },
|
|
70021
|
+
afterId: {
|
|
70022
|
+
type: "string",
|
|
70023
|
+
description: "Keyset paging token \u2014 the `nextAfterId` from the previous page."
|
|
70024
|
+
},
|
|
70025
|
+
...LIVE_ENABLED_PROPERTY
|
|
70026
|
+
}
|
|
70027
|
+
});
|
|
70028
|
+
LIVE_ZOMBIE_ACCOUNTS_INPUT_SCHEMA = Object.freeze({
|
|
70029
|
+
type: "object",
|
|
70030
|
+
properties: {
|
|
70031
|
+
minDaysInactive: {
|
|
70032
|
+
type: "integer",
|
|
70033
|
+
minimum: 0,
|
|
70034
|
+
maximum: 3650,
|
|
70035
|
+
description: "Additionally require last login older than N days (default 0 = ignore login age)."
|
|
70036
|
+
},
|
|
70037
|
+
includeAllUserTypes: { type: "boolean" },
|
|
70038
|
+
limit: { type: "integer", minimum: 1, maximum: 500 },
|
|
70039
|
+
...LIVE_ENABLED_PROPERTY
|
|
70040
|
+
}
|
|
70041
|
+
});
|
|
70042
|
+
LIVE_GROUP_MEMBERS_INPUT_SCHEMA = Object.freeze({
|
|
70043
|
+
type: "object",
|
|
70044
|
+
required: ["name"],
|
|
70045
|
+
properties: {
|
|
70046
|
+
name: {
|
|
70047
|
+
type: "string",
|
|
70048
|
+
minLength: 1,
|
|
70049
|
+
description: "Exact Group DeveloperName or Name (label) of the queue / public group."
|
|
70050
|
+
},
|
|
70051
|
+
groupType: {
|
|
70052
|
+
type: "string",
|
|
70053
|
+
enum: ["Queue", "Regular", "auto"],
|
|
70054
|
+
description: "What kind of Group `name` names. Default 'auto' matches both; an ambiguous name across both errors honestly."
|
|
70055
|
+
},
|
|
70056
|
+
expandNested: {
|
|
70057
|
+
type: "boolean",
|
|
70058
|
+
description: "Expand exactly ONE level of nested public groups (default false). Role entries are never expanded."
|
|
70059
|
+
},
|
|
70060
|
+
limit: { type: "integer", minimum: 1, maximum: 500 },
|
|
70061
|
+
...LIVE_ENABLED_PROPERTY
|
|
70062
|
+
}
|
|
70063
|
+
});
|
|
70064
|
+
LIVE_USER_PERMSETS_INPUT_SCHEMA = Object.freeze({
|
|
70065
|
+
type: "object",
|
|
70066
|
+
required: ["user"],
|
|
70067
|
+
properties: {
|
|
70068
|
+
user: {
|
|
70069
|
+
type: "string",
|
|
70070
|
+
minLength: 1,
|
|
70071
|
+
description: "Exact Username (preferred \u2014 unique) or exact User.Name. An ambiguous name returns candidates, never a guess."
|
|
70072
|
+
},
|
|
70073
|
+
limit: { type: "integer", minimum: 1, maximum: 500 },
|
|
70074
|
+
...LIVE_ENABLED_PROPERTY
|
|
70075
|
+
}
|
|
70076
|
+
});
|
|
67916
70077
|
LIVE_LICENSE_USAGE_INPUT_SCHEMA = Object.freeze({
|
|
67917
70078
|
type: "object",
|
|
67918
70079
|
properties: {
|
|
@@ -69572,7 +71733,7 @@ var init_tools = __esm({
|
|
|
69572
71733
|
},
|
|
69573
71734
|
{
|
|
69574
71735
|
name: "sfi.coverage_report",
|
|
69575
|
-
description: "Report the vault's self-assessed metadata coverage: covered, partial, not-modeled, and \u2014 during a staged refresh \u2014 pending families (queued by the in-progress tiered build, with `stagedBuild` tier progress; pending types count as missing coverage, so absence answers about them must stay qualified). Use before absence-based or destructive answers.",
|
|
71736
|
+
description: "Report the vault's self-assessed metadata coverage: covered, partial, not-modeled, and \u2014 during a staged refresh \u2014 pending families (queued by the in-progress tiered build, with `stagedBuild` tier progress; pending types count as missing coverage, so absence answers about them must stay qualified). Use before absence-based or destructive answers. The `assignmentData` section covers runtime assignment data (User / PermissionSetAssignment / GroupMember): NOT in the vault by design (a runtime data object, not a retrieve gap) \u2014 it names the consent-gated live tools that answer those questions (sfi.live_permset_holders, sfi.live_user_permsets, sfi.live_group_members, sfi.live_zombie_accounts), whether live consent is currently granted, and whether/when a counts-only facts snapshot (`sfi refresh --with-data-shape`) was captured.",
|
|
69576
71737
|
inputSchema: COVERAGE_REPORT_INPUT_SCHEMA
|
|
69577
71738
|
},
|
|
69578
71739
|
{
|
|
@@ -69582,7 +71743,7 @@ var init_tools = __esm({
|
|
|
69582
71743
|
},
|
|
69583
71744
|
{
|
|
69584
71745
|
name: "sfi.health_check",
|
|
69585
|
-
description: 'Report self-assessed server health, render consistency, and coverage completeness, plus a freshness block (vault age, a stale flag, the most recent refresh\'s change count, and a yellow-flag nudge when the vault is old or local source drifted). While a staged refresh (`sfi refresh --staged`) is mid-build, status is degraded with explicit tier progress ("building tier i/n") until the final tier clears the marker.',
|
|
71746
|
+
description: 'Report self-assessed server health, render consistency, and coverage completeness, plus a freshness block (vault age, a stale flag, the most recent refresh\'s change count, and a yellow-flag nudge when the vault is old or local source drifted). While a staged refresh (`sfi refresh --staged`) is mid-build, status is degraded with explicit tier progress ("building tier i/n") until the final tier clears the marker. Also carries an INFORMATIONAL `assignmentData` block (runtime assignment data is live-first by design \u2014 its absence never degrades status; a stale counts snapshot >30 days old earns an advisory only).',
|
|
69586
71747
|
inputSchema: HEALTH_CHECK_INPUT_SCHEMA
|
|
69587
71748
|
},
|
|
69588
71749
|
{
|
|
@@ -69662,9 +71823,29 @@ var init_tools = __esm({
|
|
|
69662
71823
|
},
|
|
69663
71824
|
{
|
|
69664
71825
|
name: "sfi.live_inactive_users",
|
|
69665
|
-
description: "Opt-in live org: active users who haven't logged in within N days (default 30) or never have \u2014 the license-reclamation / dormant-account question. Standard (human) users by default; reports the true total (`totalInactive`) plus a capped detail page, oldest-dormant first. `limit` pages the detail rows (default 100, hard cap 500) and a per-response ~36 KB byte budget trims the page further when a wide page would exceed it (the response carries both the structured rows and a rendered table, so it can't trip the global ~45 KB limit); `capped` flips true when more remain and a `note` appears when the page was byte-trimmed. Read-only; LastLoginDate is live-only state.",
|
|
71826
|
+
description: "Opt-in live org: active users who haven't logged in within N days (default 30) or never have \u2014 the license-reclamation / dormant-account question. Standard (human) users by default; reports the true total (`totalInactive`) plus a capped detail page, oldest-dormant first. `limit` pages the detail rows (default 100, hard cap 500) and a per-response ~36 KB byte budget trims the page further when a wide page would exceed it (the response carries both the structured rows and a rendered table, so it can't trip the global ~45 KB limit); `capped` flips true when more remain and a `note` appears when the page was byte-trimmed. Read-only; LastLoginDate is live-only state. Dormancy ONLY \u2014 for active users with login access but ZERO permission-set/PSG assignments (the perm-set-less variant of this sweep), use sfi.live_zombie_accounts.",
|
|
69666
71827
|
inputSchema: LIVE_INACTIVE_USERS_INPUT_SCHEMA
|
|
69667
71828
|
},
|
|
71829
|
+
{
|
|
71830
|
+
name: "sfi.live_permset_holders",
|
|
71831
|
+
description: "Opt-in live org: WHO HOLDS a permission set, permission set group, or profile \u2014 the name-by-name assignment roster (PermissionSetAssignment / User rows are runtime state, never in the vault; vault sfi.effective_permissions describes what a grantor GRANTS, not who holds it). `kind` defaults to 'auto' (probes PermissionSet \u2192 PermissionSetGroup \u2192 Profile by exact name/label; ambiguity or no-match is an honest error, never a guess). For a permission set the default `includeViaGroups: true` ALSO counts users who receive it through a PermissionSetGroup containing it (querying PermissionSetGroupComponent), reported separately as `directHolders` / `viaGroupHolders` with a deduped `totalAssignees` \u2014 direct-only rosters silently miss via-group holders (the classic wrong answer). kind 'profile' returns the User roster for the profile. Expired assignments are excluded and disclosed (`expiredExcluded`); active assignees only by default. True count first; `limit` (default 100, cap 500) + ~36 KB byte budget page the detail; keyset paging via `afterId`/`nextAfterId`; `groupBy: 'profile'` (default) adds per-profile buckets. Reverse direction (what does USER X hold) = sfi.live_user_permsets. Read-only; point-in-time as of queriedAt \u2014 never cached beyond the live-session TTL; roster is CURRENT org state, unlike vault answers.",
|
|
71832
|
+
inputSchema: LIVE_PERMSET_HOLDERS_INPUT_SCHEMA
|
|
71833
|
+
},
|
|
71834
|
+
{
|
|
71835
|
+
name: "sfi.live_zombie_accounts",
|
|
71836
|
+
description: 'Opt-in live org: ZOMBIE accounts \u2014 active users with login access but ZERO permission-set/PSG assignments, via a single SOQL anti-join (Id NOT IN PermissionSetAssignment WHERE PermissionSet.IsOwnedByProfile = false; the IsOwnedByProfile filter is load-bearing \u2014 every user carries a system PSA row for their profile-owned set). When the org rejects the anti-join it falls back to a DISCLOSED client-side diff of two bounded queries (`method: \'client-diff\'`, capped scans flagged as a lower bound). Optional `minDaysInactive` additionally requires dormancy; Standard (human) users by default. True count first (`totalZombies`); `limit` (default 100, cap 500) + ~36 KB byte budget page the detail. HONESTY: a "zombie" still holds every permission its PROFILE grants \u2014 this reports "no permission-set/PSG assignments", NOT "no access". This is the perm-set-less variant of sfi.live_inactive_users, which covers dormancy only (and for who DOES hold a set, use sfi.live_permset_holders). Read-only; point-in-time.',
|
|
71837
|
+
inputSchema: LIVE_ZOMBIE_ACCOUNTS_INPUT_SCHEMA
|
|
71838
|
+
},
|
|
71839
|
+
{
|
|
71840
|
+
name: "sfi.live_group_members",
|
|
71841
|
+
description: "Opt-in live org: WHO IS IN a queue or public group \u2014 the runtime GroupMember roster (metadata XML only carries DECLARED members, so the vault's member counts routinely say 0 while Setup-managed membership is live-only; vault sfi.empty_queues_and_groups reads the declared counts). Resolves the Group by exact DeveloperName/Name (`groupType` defaults to 'auto'; ambiguity or no-match is an honest error, never a guess). GroupMember.UserOrGroupId is polymorphic: users (005) are resolved name-by-name, nested groups (00G) are LISTED but not expanded, and Role / RoleAndSubordinates entries are surfaced by UserRole name and NEVER expanded to users. `expandNested: true` expands exactly ONE level of nested public groups, stamped `expansion: 'partial-one-level'` \u2014 deeper nesting and role-hierarchy subordinates are not enumerated; never treat a partial expansion as the full effective membership. Queues also return `supportedObjects` (QueueSobject \u2014 which sObjects the queue can own). Output cross-checks the vault: `vaultDeclaredMemberCount` vs `liveDirectMemberCount` with a `drift` boolean. True count first (`totalDirectMembers`); `limit` (default 100, cap 500) + ~36 KB byte budget page the user rows. Read-only; point-in-time as of queriedAt.",
|
|
71842
|
+
inputSchema: LIVE_GROUP_MEMBERS_INPUT_SCHEMA
|
|
71843
|
+
},
|
|
71844
|
+
{
|
|
71845
|
+
name: "sfi.live_user_permsets",
|
|
71846
|
+
description: "Opt-in live org: WHAT does USER X hold \u2014 the reverse of sfi.live_permset_holders (input = a user, output = the grantors they hold: profile + direct permission sets + permission set groups, with expirations). Resolves the user by exact Username (preferred \u2014 unique) then exact Name; an ambiguous name returns an honest candidate list, never a guess. PSA rows are split `directPermsets` vs `viaGroups` (grouped by PermissionSetGroup); the query pins PermissionSet.IsOwnedByProfile = false so the user's profile-owned system PSA row never masquerades as a direct assignment \u2014 the profile is reported as `user.profileName` instead. Expired assignments are excluded and disclosed (`expiredExcluded`). DUAL PROVENANCE: this answers WHICH grantors the user holds (live, point-in-time as of queriedAt); vault sfi.effective_permissions answers WHAT those grantors grant \u2014 pair them, each half stamped with its own provenance. True count first (`totalAssignments`); `limit` (default 200, cap 500) + ~36 KB byte budget page the detail. Read-only.",
|
|
71847
|
+
inputSchema: LIVE_USER_PERMSETS_INPUT_SCHEMA
|
|
71848
|
+
},
|
|
69668
71849
|
{
|
|
69669
71850
|
name: "sfi.live_license_usage",
|
|
69670
71851
|
description: "Opt-in live org: license / cost optimization. Returns UserLicense and PermissionSetLicense utilization (total / used / available / utilizationPct; unlimited licenses surface total: -1 \u2192 available/pct null) plus `reclaimableSeats` \u2014 active Standard users dormant past `inactiveDays` (default 90), grouped by their user license, the paid-seats-nobody-uses question. Honesty axis (verbatim): reclaimable seats is a PROXY (inactivity, not actual feature usage; some dormant seats are held intentionally; per-feature-license usage is not covered). READ-ONLY: never deprovisions or reassigns a license \u2014 verify each seat before reclaiming. License counts and LastLoginDate are live-only state.",
|
|
@@ -69757,7 +71938,7 @@ var init_tools = __esm({
|
|
|
69757
71938
|
},
|
|
69758
71939
|
{
|
|
69759
71940
|
name: "sfi.effective_permissions",
|
|
69760
|
-
description: "Compute a user's EFFECTIVE access \u2014 the UNION of a profile + assigned permission sets, max-wins, with each permission attributed to the container(s) that grant it. `why_cant_user_see_record` evaluates a single record question against a bundle; nothing else rolls the containers up into one combined ability \u2014 this does. Input: `profileId` and/or `permissionSetIds[]` (at least one). A `PermissionSetGroup:` id may be passed in `permissionSetIds[]` \u2014 it is EXPANDED into its member permission sets (declared membership) and unioned in, so a PSG-assigned user gets a real answer (a permset reachable both directly and via a group is unioned once, not double-counted). It composes each container's outgoing `grantedBy` edges (object + field + apex), `properties.userPermissions` (system perms), and `properties.recordTypeVisibilities` (record-type visibility). `objectPermissions[]` carries the OR'd `allowCreate`/`allowRead`/`allowEdit`/`allowDelete`/`viewAllRecords`/`modifyAllRecords` per object plus `grantedBy` (the containers contributing a flag); `systemPermissions[]` lists each user-permission with its `grantedBy`; `customPermissions[]` (CR-CAP-10) lists each granted custom permission with its `grantedBy` + `targetMissing` (true when the granted name has no `CustomPermission` definition in the vault \u2014 managed-package / not-retrieved; declared but not resolvable, and NOT folded into systemPermissions); `recordTypeVisibilities[]` unions each container's declared record-type visibility (max-wins \u2014 visible=true wins; `<visible>` omitted in older metadata counts as visible, only an explicit false hides), each entry `{recordType, visible, grantedBy}` \u2014 record-type visibility is part of THIS union now, no longer only the separate `recordtype_availability` surface (that tool remains for the per-object grouped view); a container carrying no extracted `recordTypeVisibilities` property (a vault refreshed before record-type extraction) contributes nothing and is DISCLOSED (re-run /sfi-refresh), never fabricated as 'no record types'; `summary` reports objects / fieldsWithFls / apexClasses / systemPermissions / customPermissions / recordTypeVisibilities counts. The object list PAGES (`limit` default 100 / max 200, `offset`/`hasMore`/`truncated`). `declared` confidence. `disclosures` is explicit about the boundaries: permission-set GROUP membership IS expanded, but muting permission sets are DISCLOSED, not subtracted (effective access may be lower); app/tab visibility is a SEPARATE surface (now extracted \u2014 see `app_access` / `tab_availability`), not part of this union; field-level detail is summarised (use `field_access_audit`); object permission is NOT record access (record visibility needs OWD + sharing); custom permissions are declared grants, NOT system userPermissions, so they are never double-counted. Missing containers are ignored with a disclosure; if none exist \u2192 `component-not-found`.",
|
|
71941
|
+
description: "Compute a user's EFFECTIVE access \u2014 the UNION of a profile + assigned permission sets, max-wins, with each permission attributed to the container(s) that grant it. `why_cant_user_see_record` evaluates a single record question against a bundle; nothing else rolls the containers up into one combined ability \u2014 this does. Input: `profileId` and/or `permissionSetIds[]` (at least one). A `PermissionSetGroup:` id may be passed in `permissionSetIds[]` \u2014 it is EXPANDED into its member permission sets (declared membership) and unioned in, so a PSG-assigned user gets a real answer (a permset reachable both directly and via a group is unioned once, not double-counted). It composes each container's outgoing `grantedBy` edges (object + field + apex), `properties.userPermissions` (system perms), and `properties.recordTypeVisibilities` (record-type visibility). `objectPermissions[]` carries the OR'd `allowCreate`/`allowRead`/`allowEdit`/`allowDelete`/`viewAllRecords`/`modifyAllRecords` per object plus `grantedBy` (the containers contributing a flag); `systemPermissions[]` lists each user-permission with its `grantedBy`; `customPermissions[]` (CR-CAP-10) lists each granted custom permission with its `grantedBy` + `targetMissing` (true when the granted name has no `CustomPermission` definition in the vault \u2014 managed-package / not-retrieved; declared but not resolvable, and NOT folded into systemPermissions); `recordTypeVisibilities[]` unions each container's declared record-type visibility (max-wins \u2014 visible=true wins; `<visible>` omitted in older metadata counts as visible, only an explicit false hides), each entry `{recordType, visible, grantedBy}` \u2014 record-type visibility is part of THIS union now, no longer only the separate `recordtype_availability` surface (that tool remains for the per-object grouped view); a container carrying no extracted `recordTypeVisibilities` property (a vault refreshed before record-type extraction) contributes nothing and is DISCLOSED (re-run /sfi-refresh), never fabricated as 'no record types'; `summary` reports objects / fieldsWithFls / apexClasses / systemPermissions / customPermissions / recordTypeVisibilities counts. The object list PAGES (`limit` default 100 / max 200, `offset`/`hasMore`/`truncated`). `declared` confidence. `disclosures` is explicit about the boundaries: permission-set GROUP membership IS expanded, but muting permission sets are DISCLOSED, not subtracted (effective access may be lower); app/tab visibility is a SEPARATE surface (now extracted \u2014 see `app_access` / `tab_availability`), not part of this union; field-level detail is summarised (use `field_access_audit`); object permission is NOT record access (record visibility needs OWD + sharing); custom permissions are declared grants, NOT system userPermissions, so they are never double-counted. Missing containers are ignored with a disclosure; if none exist \u2192 `component-not-found`. DIRECTION: this answers WHAT a given container bundle GRANTS (vault metadata); WHICH sets/PSGs a specific USER actually holds right now is runtime assignment state \u2014 use sfi.live_user_permsets (live, read-only) and feed its grantors back into this tool for a dual-provenance answer.",
|
|
69761
71942
|
inputSchema: EFFECTIVE_PERMISSIONS_INPUT_SCHEMA
|
|
69762
71943
|
},
|
|
69763
71944
|
{
|
|
@@ -70017,12 +72198,12 @@ var init_tools = __esm({
|
|
|
70017
72198
|
},
|
|
70018
72199
|
{
|
|
70019
72200
|
name: "sfi.unassigned_permission_sets",
|
|
70020
|
-
description: "v2.4 hygiene tool: list PermissionSets unassigned to users. The tool ships TWO output paths: (1) when v1.7 R2 Tooling-API enrichment has run, reads `properties.assignedUserCount` as the authoritative answer (PermissionSets with count 0 surface in `unassigned[]`); (2) when enrichment has not run, falls back to a structural check \u2014 PermissionSets with no outgoing `grantedBy` edges surface as `orphanedFromComponents[]`. The `unassignedCount` field counts confirmed unassigned; `unknownAssignmentCount` separately tallies PermissionSets where assignment cannot be determined. `enrichmentStatus` reports which path the answer came from (`tooling-api-fresh` / `tooling-api-stale` / `structural-only` / `no-assignment-data`). Honesty axis (v2.4 constitutional): NEVER counts unknownAssignmentCount toward unassignedCount \u2014 separates 'no data' from 'no assignments'. Emits a `coverageCaveat` when the PermissionSet family was not retrieved (distinct from the unknownAssignmentCount enrichment axis); empty results under it are 'not retrieved'. When the vault holds a captured permission-holder aggregate (`refresh --with-data-shape`), the response embeds a `dataShape` holders block (`data_snapshot`, COUNTS ONLY \u2014 no identities): a container absent from the org-wide aggregate had FACTUALLY zero active assignments at the capture stamp, upgrading the metadata inference.",
|
|
72201
|
+
description: "v2.4 hygiene tool: list PermissionSets unassigned to users. The tool ships TWO output paths: (1) when v1.7 R2 Tooling-API enrichment has run, reads `properties.assignedUserCount` as the authoritative answer (PermissionSets with count 0 surface in `unassigned[]`); (2) when enrichment has not run, falls back to a structural check \u2014 PermissionSets with no outgoing `grantedBy` edges surface as `orphanedFromComponents[]`. The `unassignedCount` field counts confirmed unassigned; `unknownAssignmentCount` separately tallies PermissionSets where assignment cannot be determined. `enrichmentStatus` reports which path the answer came from (`tooling-api-fresh` / `tooling-api-stale` / `structural-only` / `no-assignment-data`). Honesty axis (v2.4 constitutional): NEVER counts unknownAssignmentCount toward unassignedCount \u2014 separates 'no data' from 'no assignments'. Emits a `coverageCaveat` when the PermissionSet family was not retrieved (distinct from the unknownAssignmentCount enrichment axis); empty results under it are 'not retrieved'. When the vault holds a captured permission-holder aggregate (`refresh --with-data-shape`), the response embeds a `dataShape` holders block (`data_snapshot`, COUNTS ONLY \u2014 no identities): a container absent from the org-wide aggregate had FACTUALLY zero active assignments at the capture stamp, upgrading the metadata inference. For the current name-by-name holder list of one specific container, use sfi.live_permset_holders (opt-in, read-only).",
|
|
70021
72202
|
inputSchema: UNASSIGNED_PERMISSION_SETS_INPUT_SCHEMA
|
|
70022
72203
|
},
|
|
70023
72204
|
{
|
|
70024
72205
|
name: "sfi.empty_queues_and_groups",
|
|
70025
|
-
description: "v2.4 hygiene tool: list Queue and Group nodes with zero members. Walks `properties.memberCount` (the v1.1+ extractor convention) and falls back to `properties.queueMembers` / `properties.groupMembers` array length. The 'routing trap' case \u2014 a Queue with zero members but multiple incoming AssignmentRule references \u2014 surfaces with `incomingAssignmentRuleCount > 0`; admins must reassign routing before deletion. The `isLikelyStale` flag combines zero members + incoming refs + `lastModifiedAt > 180 days`. Member resolution that cannot decide ('unknown') is counted in `unknownMemberCountQueues` / `unknownMemberCountGroups`, NEVER toward emptiness. Honesty axis (verbatim): runtime membership changes via the Setup UI since the last vault refresh are not reflected. Emits a `coverageCaveat` (scoped to the type filter) when Queue/Group was not retrieved; empty lists under it are 'not checked', not 'none'.",
|
|
72206
|
+
description: "v2.4 hygiene tool: list Queue and Group nodes with zero members. Walks `properties.memberCount` (the v1.1+ extractor convention) and falls back to `properties.queueMembers` / `properties.groupMembers` array length. The 'routing trap' case \u2014 a Queue with zero members but multiple incoming AssignmentRule references \u2014 surfaces with `incomingAssignmentRuleCount > 0`; admins must reassign routing before deletion. The `isLikelyStale` flag combines zero members + incoming refs + `lastModifiedAt > 180 days`. Member resolution that cannot decide ('unknown') is counted in `unknownMemberCountQueues` / `unknownMemberCountGroups`, NEVER toward emptiness. Honesty axis (verbatim): runtime membership changes via the Setup UI since the last vault refresh are not reflected \u2014 for the CURRENT runtime roster (and a measured vault-vs-live drift check), use sfi.live_group_members (opt-in, read-only). Emits a `coverageCaveat` (scoped to the type filter) when Queue/Group was not retrieved; empty lists under it are 'not checked', not 'none'.",
|
|
70026
72207
|
inputSchema: EMPTY_QUEUES_AND_GROUPS_INPUT_SCHEMA
|
|
70027
72208
|
},
|
|
70028
72209
|
{
|
|
@@ -70506,6 +72687,14 @@ var init_tools = __esm({
|
|
|
70506
72687
|
return runTool(ctx, args, liveOrgLimitsInputSchema, liveOrgLimitsHandler);
|
|
70507
72688
|
case "sfi.live_inactive_users":
|
|
70508
72689
|
return runTool(ctx, args, liveInactiveUsersInputSchema, liveInactiveUsersHandler);
|
|
72690
|
+
case "sfi.live_permset_holders":
|
|
72691
|
+
return runTool(ctx, args, livePermsetHoldersInputSchema, livePermsetHoldersHandler);
|
|
72692
|
+
case "sfi.live_zombie_accounts":
|
|
72693
|
+
return runTool(ctx, args, liveZombieAccountsInputSchema, liveZombieAccountsHandler);
|
|
72694
|
+
case "sfi.live_group_members":
|
|
72695
|
+
return runTool(ctx, args, liveGroupMembersInputSchema, liveGroupMembersHandler);
|
|
72696
|
+
case "sfi.live_user_permsets":
|
|
72697
|
+
return runTool(ctx, args, liveUserPermsetsInputSchema, liveUserPermsetsHandler);
|
|
70509
72698
|
case "sfi.live_license_usage":
|
|
70510
72699
|
return runTool(ctx, args, liveLicenseUsageInputSchema, liveLicenseUsageHandler);
|
|
70511
72700
|
case "sfi.live_consent":
|
|
@@ -72446,7 +74635,7 @@ var init_package_version = __esm({
|
|
|
72446
74635
|
"use strict";
|
|
72447
74636
|
readCliPackageVersion = () => {
|
|
72448
74637
|
if (true)
|
|
72449
|
-
return "0.1.
|
|
74638
|
+
return "0.1.24";
|
|
72450
74639
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
72451
74640
|
try {
|
|
72452
74641
|
const raw = readFileSync3(fileURLToPath(new URL(rel, import.meta.url)), "utf8");
|
|
@@ -142342,13 +144531,13 @@ var registerMcpCommand = (program) => {
|
|
|
142342
144531
|
process.env["SFI_PLUGIN_VERSION"] = readCliPackageVersion();
|
|
142343
144532
|
process.stderr.write(`sfi mcp: serving vault ${vaultRoot}${targetOrg !== null ? ` (org: ${targetOrg})` : " (no targetOrg in config)"}
|
|
142344
144533
|
`);
|
|
142345
|
-
|
|
142346
|
-
const notice = formatUpdateNotice(
|
|
144534
|
+
void checkForUpdate(readCliPackageVersion()).then((result) => {
|
|
144535
|
+
const notice = formatUpdateNotice(result);
|
|
142347
144536
|
if (notice !== null)
|
|
142348
144537
|
process.stderr.write(`sfi mcp: ${notice}
|
|
142349
144538
|
`);
|
|
142350
|
-
}
|
|
142351
|
-
}
|
|
144539
|
+
}, () => {
|
|
144540
|
+
});
|
|
142352
144541
|
const shutdownOnce = makeShutdownOnce(ctx);
|
|
142353
144542
|
for (const signal of SHUTDOWN_SIGNALS) {
|
|
142354
144543
|
process.on(signal, () => {
|
|
@@ -142370,7 +144559,7 @@ var makeShutdownOnce = (ctx) => {
|
|
|
142370
144559
|
|
|
142371
144560
|
// dist/src/commands/demo.js
|
|
142372
144561
|
init_refresh();
|
|
142373
|
-
var buildVersion = () => true ? "0.1.
|
|
144562
|
+
var buildVersion = () => true ? "0.1.24" : "dev";
|
|
142374
144563
|
var SHUTDOWN_SIGNALS2 = ["SIGINT", "SIGTERM"];
|
|
142375
144564
|
var resolveDemoSource = () => {
|
|
142376
144565
|
let dir = dirname21(fileURLToPath2(import.meta.url));
|
|
@@ -143355,7 +145544,7 @@ init_vault_git();
|
|
|
143355
145544
|
init_watch();
|
|
143356
145545
|
var readVersion = () => {
|
|
143357
145546
|
if (true)
|
|
143358
|
-
return "0.1.
|
|
145547
|
+
return "0.1.24";
|
|
143359
145548
|
const pkgUrl = new URL("../../package.json", import.meta.url);
|
|
143360
145549
|
const raw = readFileSync6(fileURLToPath3(pkgUrl), "utf8");
|
|
143361
145550
|
const parsed = JSON.parse(raw);
|