import { NowConfig, ObjectShape, MODULE_RESOLUTION, Plugin, Shape, path, FileSystem, type Record as SdkRecord, type Factory, type Diagnostics, } from '@servicenow/sdk-build-core' import { JsonFileShape } from './json-plugin' import { NowAttachShape } from './now-attach-plugin' export class NowConfigShape extends ObjectShape {} // ============================================================================ // Application Runtime Policy Helper Functions // ============================================================================ /** * Maps Fluent mode values to ServiceNow XML state values for performance policy */ const PERFORMANCE_MODE_MAP: Record = { disabled: 'disabled', enforced: 'enforced', logOnly: 'log_only', } /** * Reverse mapping: ServiceNow XML state values to Fluent mode values */ const PERFORMANCE_MODE_REVERSE_MAP: Record = { disabled: 'disabled', enforced: 'enforced', log_only: 'logOnly', } /** * Maps applicationRuntimePolicy to performance policy mode */ const ARP_TO_PERFORMANCE_POLICY_MODE_MAP: Record = { none: 'disabled', tracking: 'log_only', enforcing: 'enforced', } /** * Maps Application Runtime Policy values to required Runtime Access Tracking values * - none: RAT can be anything (no restriction) * - tracking: RAT must be permissive * - enforcing: RAT must be enforcing */ const ARP_TO_RAT_MAP: Record = { none: null, // No restriction - RAT can be any value tracking: 'permissive', enforcing: 'enforcing', } // ============================================================================ // Network Policy Validation Functions // ============================================================================ /** * Validates host field format and content * Uses URL API for parsing with manual wildcard validation (URL can't handle wildcards) * Matches platform business rule validation logic */ function validateHost(host: string | undefined, policyType: string, diagnostics: Diagnostics, source: Shape): boolean { if (!host || !host.trim()) { return true } const hostValue = host.trim() // Use URL API for scheme/port parsing — replace wildcards with placeholder since URL can't handle them const parseableUrl = hostValue.replace(/\*/g, 'wildcard-placeholder') let url: URL try { url = new URL(parseableUrl) } catch { // URL throws for invalid ports (>65535) among other reasons // Check if the host has an out-of-range port and diagnose it const portMatch = hostValue.match(/:(\d+)\s*$/) if (portMatch) { const port = parseInt(portMatch[1]!, 10) if (isNaN(port) || port < 1 || port > 65535) { diagnostics.error(source, `Invalid port "${portMatch[1]}". Must be 1-65535.`) return false } } // Zod regex handles basic format validation; if URL can't parse for other reasons, skip return true } const scheme = url.protocol.slice(0, -1) // Remove trailing ':' // Validate scheme based on policy type // Platform uses system properties to configure allowed schemes, // but we use hardcoded defaults here for build-time validation const allowedSchemes = getValidSchemes(policyType) if (!allowedSchemes.includes(scheme)) { diagnostics.error( source, `Invalid URL scheme "${scheme}" for policy type "${policyType}". Allowed schemes: ${allowedSchemes.join(', ')}` ) return false } // Validate wildcards on original hostname (not the placeholder) const originalHostname = url.hostname.replace(/wildcard-placeholder/g, '*') if (!validateWildcards(originalHostname, diagnostics, source)) { return false } // Validate port range using URL's parsed port if (url.port) { const port = parseInt(url.port, 10) if (port < 1 || port > 65535) { diagnostics.error(source, `Invalid port "${url.port}". Must be 1-65535.`) return false } } return true } /** * Gets valid URL schemes based on policy type * Matches platform business rule defaults for glide.arp.csp.allowed.url.schemes * and glide.arp.server.outbound.allowed.url.schemes */ function getValidSchemes(policyType: string): string[] { // CSP script-src only allows HTTPS if (policyType === 'csp_script_src') { return ['https'] } // CSP connect-src allows HTTPS and WSS if (policyType === 'csp_connect_src') { return ['https', 'wss'] } // Server outbound allows all four protocols if (policyType === 'now_outbound') { return ['http', 'https', 'ws', 'wss'] } // For inbound policies, host should be empty, but if provided, use https return ['https'] } /** * Validates wildcard patterns in hostname */ function validateWildcards(hostPart: string, diagnostics: Diagnostics, source: Shape): boolean { if (hostPart === '*') { diagnostics.error(source, 'Single wildcard "*" not allowed. Use "*.example.com" format.') return false } if (hostPart.indexOf('*') === -1) { return true } const parts = hostPart.split('.') let wildcardCount = 0 for (let i = 0; i < parts.length; i++) { const part = parts[i]! if (part === '*') { wildcardCount++ if (i !== 0 || wildcardCount > 1) { diagnostics.error(source, `Wildcard must be first segment only. Invalid: "${hostPart}"`) return false } } else if (part.indexOf('*') !== -1) { diagnostics.error(source, `Partial wildcards not allowed: "${part}"`) return false } } return true } /** * Validates path field format * Each path in the array must start with single forward slash */ function validatePath(path: string[] | undefined, diagnostics: Diagnostics, source: Shape): boolean { if (!path || path.length === 0) { return true } for (const singlePath of path) { const trimmedPath = singlePath.trim() if (!trimmedPath.startsWith('/') || trimmedPath.startsWith('//')) { diagnostics.error(source, `Path must start with single "/": "${trimmedPath}"`) return false } } return true } /** * Validates network policy fields for cross-field and business rules */ function validateNetworkPolicy( policy: { policyType?: unknown; host?: unknown; path?: unknown }, diagnostics: Diagnostics, source: Shape ): boolean { let isValid = true const policyType = typeof policy.policyType === 'string' ? policy.policyType : '' const host = typeof policy.host === 'string' ? policy.host : undefined const path = Array.isArray(policy.path) ? policy.path : undefined if (host && !validateHost(host, policyType, diagnostics, source)) { isValid = false } if (path && !validatePath(path, diagnostics, source)) { isValid = false } return isValid } /** * Creates network policy records from config */ async function createNetworkPolicyRecords( networkPolicies: Shape[], factory: Factory, source: Shape, diagnostics: Diagnostics ): Promise { const records: SdkRecord[] = [] for (const policy of networkPolicies) { const policyObj = policy.asObject() // Validate policy fields (complex validations that can't be done in schema) const policyData = { policyType: policyObj.get('policyType')?.getValue(), host: policyObj.get('host')?.getValue(), path: policyObj.get('path')?.getValue(), } validateNetworkPolicy(policyData, diagnostics, policy) const record = await factory.createRecord({ source: source, table: 'sys_arp_network_policy', explicitId: policyObj.get('$id'), properties: policyObj.transform(({ $ }) => ({ active: $.def(true), policy_type: $.from('policyType'), status: $, host: $, scheme: $, // Join path array with newlines for XML storage path: $.from('path').map( (pathShape) => pathShape .ifArray() ?.getElements() .map((e: Shape) => e.getValue()) .join('\n') ?? '' ), resource: $, short_description: $.from('shortDescription'), })), }) records.push(record) } return records } /** * Creates wildcard policy record from config */ async function createWildcardPolicyRecord( wildcardPolicy: ObjectShape, factory: Factory, source: Shape, scopeId: string, diagnostics: Diagnostics ): Promise { // Extract nested pillar objects const networkPillar = wildcardPolicy.get('network')?.ifDefined()?.asObject() const scriptingPillar = wildcardPolicy.get('scripting')?.ifDefined()?.asObject() const arlPillar = wildcardPolicy.get('arl')?.ifDefined()?.asObject() // Validate pillar active/wildcard consistency const networkActive = networkPillar?.get('active')?.toBoolean().getValue() ?? false const networkWildcardShape = networkPillar?.get('networkWildcard')?.ifArray() const networkWildcard = networkWildcardShape?.getElements() if (!networkActive && networkWildcard && networkWildcard.length > 0) { diagnostics.warn(networkWildcardShape!, 'networkWildcard values will be ignored when network.active is false') } if (networkActive && (!networkWildcard || networkWildcard.length === 0)) { diagnostics.hint( networkPillar!.get('active'), 'Network pillar is active but has no networkWildcard selections. Consider adding networkWildcard values or setting active to false.' ) } const scriptingActive = scriptingPillar?.get('active')?.toBoolean().getValue() ?? false const scriptingWildcardShape = scriptingPillar?.get('scriptingWildcard')?.ifArray() const scriptingWildcard = scriptingWildcardShape?.getElements() if (!scriptingActive && scriptingWildcard && scriptingWildcard.length > 0) { diagnostics.warn( scriptingWildcardShape!, 'scriptingWildcard values will be ignored when scripting.active is false' ) } if (scriptingActive && (!scriptingWildcard || scriptingWildcard.length === 0)) { diagnostics.hint( scriptingPillar!.get('active'), 'Scripting pillar is active but has no scriptingWildcard selections. Consider adding scriptingWildcard values or setting active to false.' ) } const arlActive = arlPillar?.get('active')?.toBoolean().getValue() ?? false const arlWildcardShape = arlPillar?.get('arlWildcard')?.ifArray() const arlWildcard = arlWildcardShape?.getElements() if (!arlActive && arlWildcard && arlWildcard.length > 0) { diagnostics.warn(arlWildcardShape!, 'arlWildcard values will be ignored when arl.active is false') } if (arlActive && (!arlWildcard || arlWildcard.length === 0)) { diagnostics.hint( arlPillar!.get('active'), 'ARL pillar is active but has no arlWildcard selections. Consider adding arlWildcard values or setting active to false.' ) } return await factory.createRecord({ source: source, table: 'sys_arp_segment_policy', explicitId: wildcardPolicy.get('$id'), properties: wildcardPolicy.transform(({ $ }) => ({ sys_scope: $.val(scopeId), active: $.def(false), short_description: $.from('shortDescription').def(''), arp_record: $.from('record').toBoolean().def(false), // Network pillar arp_network: $.val(networkPillar?.get('active')?.toBoolean().getValue() ?? false), arp_network_wildcard: $.val( networkPillar ?.get('networkWildcard') ?.ifArray() ?.getElements() .map((e) => e.getValue()) .join(',') ?? '' ), // Scripting pillar arp_script: $.val(scriptingPillar?.get('active')?.toBoolean().getValue() ?? false), arp_script_wildcard: $.val( scriptingPillar ?.get('scriptingWildcard') ?.ifArray() ?.getElements() .map((e) => e.getValue()) .join(',') ?? '' ), // ARL pillar arp_arl: $.val(arlPillar?.get('active')?.toBoolean().getValue() ?? false), arp_arl_wildcard: $.val( arlPillar ?.get('arlWildcard') ?.ifArray() ?.getElements() .map((e) => e.getValue()) .join(',') ?? '' ), })), }) } /** * Creates performance policy record from config * Mode is auto-derived from applicationRuntimePolicy if not explicitly set */ async function createPerformancePolicyRecord( performancePolicy: ObjectShape, factory: Factory, source: Shape, scopeId: string, applicationRuntimePolicy: string, diagnostics: Diagnostics ): Promise { // Derive state from applicationRuntimePolicy const derivedMode = ARP_TO_PERFORMANCE_POLICY_MODE_MAP[applicationRuntimePolicy] ?? 'log_only' // Check if user explicitly set mode and it differs from auto-derived value const explicitModeShape = performancePolicy.get('mode').ifDefined() if (explicitModeShape) { const explicitMode = explicitModeShape.toString().getValue() const explicitModeInXml = PERFORMANCE_MODE_MAP[explicitMode] if (explicitModeInXml && explicitModeInXml !== derivedMode) { const derivedModeInFluent = Object.entries(PERFORMANCE_MODE_MAP).find( ([_, xml]) => xml === derivedMode )?.[0] diagnostics.warn( explicitModeShape, `Performance policy mode '${explicitMode}' differs from auto-derived mode '${derivedModeInFluent}' based on applicationRuntimePolicy='${applicationRuntimePolicy}'. The explicit mode will be used.` ) } } return await factory.createRecord({ source: source, table: 'sys_app_resource_limit_template', explicitId: performancePolicy.get('$id'), properties: performancePolicy.transform(({ $ }) => ({ template_name: $.from('name'), // Auto-derive appCondition to query current scope app_condition: $.val(`sys_id=${scopeId}`), scheduled_job_limit: $.from('scheduledJobLimit').def(20), event_handler_limit: $.from('eventHandlerLimit').def(20), api_transaction_limit: $.from('apiTransactionLimit').def(30), interactive_transaction_limit: $.from('interactiveTransactionLimit').def(30), // State is auto-derived from applicationRuntimePolicy unless explicitly overridden state: $.from('mode') .map((v) => { const explicitMode = v.getValue() as string const mappedMode = PERFORMANCE_MODE_MAP[explicitMode] if (explicitMode && !mappedMode) { // Invalid mode value - should have been caught by schema validation // but add a diagnostic warning just in case diagnostics.warn( v, `Invalid mode value '${explicitMode}'. Valid values are: disabled, enforced, logOnly. Using auto-derived mode instead.` ) } return mappedMode ?? derivedMode }) .def(derivedMode), enable_auto_gen_limits: $.val(true), // Always enabled, hidden from user order: $.val(100), // Default order, hidden from user })), }) } // ============================================================================ export const NowConfigPlugin = Plugin.create({ name: 'NowConfigPlugin', noTelemetry: true, files: [ { matcher: /\Wnow\.config\.json$/, entryPoint: true, }, ], records: { sys_app: { relationships: { sys_arp_network_policy: { via: 'sys_scope', descendant: true, }, sys_arp_segment_policy: { via: 'sys_scope', descendant: true, }, sys_app_resource_limit_template: { via: 'sys_scope', descendant: true, }, }, async toShape(record, { packageJson, config, descendants, fs, project, diagnostics }) { // `logo` is an image field with it's content stored as an attachment // If Project#getAttachmentsForFields replaces the raw sys_id string with a decoded NowAttachShape, // persist the decoded bytes as a real project file, and store the (project-relative) file path // else store value as before const logoField = record.get('logo') let logoValue: string if (logoField instanceof NowAttachShape) { let logoPathWithoutExtension = path.join(config.generatedDir, 'other', 'sys_app', 'logo') // If config.logo references a file path on disk, // update the existing file else create a new file. const existingLogo = config.logo const existingLogoPath = existingLogo && !/^[0-9a-f]{32}$/.test(existingLogo) ? existingLogo : undefined if (existingLogoPath) { logoPathWithoutExtension = existingLogoPath.slice( 0, existingLogoPath.length - path.extname(existingLogoPath).length ) // If the logo's image format changed (e.g. png -> ico), // the old file is renamed with the new extension and updated const newLogoPath = `${logoPathWithoutExtension}${logoField.getExtension()}` if (newLogoPath !== existingLogoPath) { const oldAbsoluteLogoPath = project.resolvePath(existingLogoPath) if (FileSystem.existsSync(fs, oldAbsoluteLogoPath)) { fs.renameSync(oldAbsoluteLogoPath, project.resolvePath(newLogoPath)) diagnostics.warn( record, `Renamed file '${existingLogoPath}' to '${newLogoPath}'. Update other references to the old path.` ) } } } logoValue = await logoField.writeToPath(fs, project, logoPathWithoutExtension) } else { logoValue = logoField.toString().getValue() } const networkPolicyRecords = descendants.query('sys_arp_network_policy') const networkPolicies = networkPolicyRecords.map((policyRecord) => policyRecord.transform(({ $ }) => ({ policyType: $.from('policy_type'), active: $.toBoolean().def(true), status: $.map((v) => v.getValue() || 'requested'), host: $.toString().def(''), scheme: $.toString().def(''), resource: $.toString().def(''), shortDescription: $.from('short_description').toString().def(''), path: $.map( (v) => v .ifString() ?.split('\n') // ServiceNow stores multi-line fields with CRLF (\r\n) which get XML-encoded // as \n . fast-xml-parser doesn't decode numeric character references, so // strip them manually before splitting into an array. // TODO: Should we handle this in the parser? .map((p) => p.replace(/ /g, '').trim()) .filter((p) => p) ?? [] ).def([]), })) ) function splitCommaList(value: Shape) { return value .toString() .split(',') .map((s) => s.trim()) .filter((s) => s) } const [segmentRecord] = descendants.query('sys_arp_segment_policy') const wildcardPolicy = segmentRecord?.transform(({ $ }) => ({ active: $.toBoolean().def(false), shortDescription: $.from('short_description').toString().def(''), record: $.from('arp_record').toBoolean().def(false), network: $.from('arp_network', 'arp_network_wildcard') .map((active, wc) => ({ active: active.toBoolean(), networkWildcard: wc.pipe(splitCommaList), })) .def({ active: false, networkWildcard: [] }), scripting: $.from('arp_script', 'arp_script_wildcard') .map((active, wc) => ({ active: active.toBoolean(), scriptingWildcard: wc.pipe(splitCommaList), })) .def({ active: false, scriptingWildcard: [] }), arl: $.from('arp_arl', 'arp_arl_wildcard') .map((active, wc) => ({ active: active.toBoolean(), arlWildcard: wc.pipe(splitCommaList), })) .def({ active: false, arlWildcard: [] }), })) const arpValue = record.get('application_runtime_policy').ifString()?.getValue() || 'none' const autoDerivedModeXml = ARP_TO_PERFORMANCE_POLICY_MODE_MAP[arpValue] ?? 'log_only' const [limitRecord] = descendants.query('sys_app_resource_limit_template') const performancePolicy = limitRecord?.transform(({ $ }) => ({ name: $.from('template_name').toString(), scheduledJobLimit: $.from('scheduled_job_limit').toNumber().def(20), eventHandlerLimit: $.from('event_handler_limit').toNumber().def(20), apiTransactionLimit: $.from('api_transaction_limit').toNumber().def(30), interactiveTransactionLimit: $.from('interactive_transaction_limit').toNumber().def(30), mode: $.from('state') .map((v) => PERFORMANCE_MODE_REVERSE_MAP[v.toString().getValue()] ?? v.toString().ifNotEmpty()) .def(PERFORMANCE_MODE_REVERSE_MAP[autoDerivedModeXml] ?? autoDerivedModeXml), })) return { success: true, value: new NowConfigShape({ source: record, properties: record.transform(({ $ }) => ({ // Required fields scope: $.from('scope').toString(), scopeId: $.val(record.getId().getValue()), name: $.from('name').toString().def(packageJson.name), active: $.toBoolean().def(true), applicationRuntimePolicy: $.val(arpValue).def('none'), networkPolicies: $.val(networkPolicies).def([]), wildcardPolicy: $.val(wildcardPolicy), performancePolicy: $.val(performancePolicy), accessControls: $.from( 'scoped_administration', 'restrict_table_access', 'can_edit_in_studio', 'runtime_access_tracking', 'private', 'trackable', 'uninstall_blocked', 'hide_on_ui', 'user_role' ) .def({ scopedAdministration: false, restrictTableAccess: false, canEditInStudio: true, runtimeAccessTracking: 'permissive', private: false, trackable: true, uninstallBlocked: false, hideOnUI: false, userRole: '', }) .map( ( scoped_administration, restrict_table_access, can_edit_in_studio, runtime_access_tracking, p, trackable, uninstall_blocked, hide_on_ui, user_role ) => ({ scopedAdministration: scoped_administration.ifDefined()?.toBoolean(), restrictTableAccess: restrict_table_access.ifDefined()?.toBoolean(), canEditInStudio: can_edit_in_studio.ifDefined()?.toBoolean(), runtimeAccessTracking: runtime_access_tracking.toString().getValue() === '' ? 'none' : runtime_access_tracking.toString(), private: p.ifDefined()?.toBoolean(), trackable: trackable.ifDefined()?.toBoolean(), uninstallBlocked: uninstall_blocked.ifDefined()?.toBoolean(), hideOnUI: hide_on_ui.ifDefined()?.toBoolean(), userRole: user_role.toString(), }) ), licensing: $.from( 'licensable', 'enforce_license', 'license_model', 'subscription_entitlement', 'license_category' ) .def({ licensable: true, enforceLicense: 'log', licenseModel: 'none', subscriptionEntitlement: '', licenseCategory: 'none', }) .map( ( licensable, enforce_license, license_model, subscription_entitlement, license_category ) => ({ licensable: licensable.ifDefined()?.toBoolean(), enforceLicense: enforce_license.toString().getValue() ? enforce_license.toString() : 'log', licenseModel: license_model.toString().getValue() ? license_model.toString() : 'none', subscriptionEntitlement: subscription_entitlement.toString(), licenseCategory: license_category.toString().getValue() ? license_category.toString() : 'none', }) ), jsLevel: $.from('js_level').toString().def('es_latest'), menu: $.toString().def(''), description: $.from('short_description').toString().def(''), logo: $.val(logoValue).def(''), guidedSetupGuid: $.from('guided_setup_guid').toString().def(''), installedAsDependency: $.from('installed_as_dependency').toBoolean().def(false), packageResolverVersion: $.from('package_resolver_version').def( config.scope === 'global' ? MODULE_RESOLUTION.V2 : MODULE_RESOLUTION.V1 ), sysCode: $.from('sys_code').def(''), })), }), } }, }, sys_arp_network_policy: { coalesce: ['policy_type', 'host', 'scheme', 'path', 'resource'], }, sys_arp_segment_policy: { coalesce: ['sys_scope'], }, sys_app_resource_limit_template: { coalesce: ['template_name'], }, }, shapes: [ { shape: NowConfigShape, async commit(shape, target, { transform, commit }) { const targetShape = await transform.toShape(target) if (!targetShape.success || !targetShape.value.isObject()) { return { success: false } } await commit(targetShape.value.merge(shape), target) return { success: true } }, }, { shape: JsonFileShape, async toRecord(file, { factory, config: rawConfig, packageJson, diagnostics, fs, project }) { if (file.getBaseName() !== NowConfig.FILE_NAME) { return { success: false } } if (rawConfig.type === 'configuration') { return { success: false } } const config = Shape.from(file.getJson(), rawConfig).asObject() const scope = config.get('scope').asString().getValue() const { name: packageName, version } = packageJson const name = config.get('name').isDefined() ? config.get('name').asString().getValue() : packageName let packageJsonPath: string try { packageJsonPath = NowConfig.moduleResolutionPath(rawConfig, packageJson, false, 'package.json') } catch (e) { diagnostics.error(file, (e as Error).message) } const accessControls = config.get('accessControls').ifDefined()?.asObject() const licensing = config.get('licensing').ifDefined()?.asObject() // Application Runtime Policy configuration const applicationRuntimePolicy = config.get('applicationRuntimePolicy').ifString()?.getValue() ?? 'none' const networkPoliciesShape = config.get('networkPolicies')?.ifArray()?.getElements() ?? [] const wildcardPolicyShape = config.get('wildcardPolicy')?.ifDefined()?.asObject() const performancePolicyShape = config.get('performancePolicy')?.ifDefined()?.asObject() const hasPolicyDefinitions = networkPoliciesShape.length > 0 || wildcardPolicyShape || performancePolicyShape // Warn if ARP is 'none' but policies are defined if (applicationRuntimePolicy === 'none' && hasPolicyDefinitions) { diagnostics.warn( config, `Application Runtime Policy is set to 'none'. Policy records will be created but the ServiceNow platform will not enforce them. Set applicationRuntimePolicy to 'tracking' or 'enforcing' to enable policy enforcement.` ) } // if `logo` is a project-relative path to the real image file, convert to NowAttachShape. // For backwards compatibility, a sys_id is also still accepted and passed through as-is. const logoValue = config.get('logo').ifDefined()?.asString().getValue() let logoShape: NowAttachShape | undefined let logoId = '' if (logoValue && /^[0-9a-f]{32}$/.test(logoValue)) { logoId = logoValue } else if (logoValue) { const logoPath = logoValue const absoluteLogoPath = project.resolvePath(logoPath) if (FileSystem.existsSync(fs, absoluteLogoPath)) { logoShape = await NowAttachShape.create(config, fs, absoluteLogoPath) } else { diagnostics.error( config.get('logo'), `Logo file not found at "${logoPath}". The sys_app.logo attachment cannot be regenerated.` ) } } // Create sys_app record const sysAppRecord = await factory.createRecord({ source: config, table: 'sys_app', properties: config.transform(({ $ }) => ({ active: $.toBoolean().def(true), // Application Runtime Policy field - only include if not 'none' (default) ...(applicationRuntimePolicy && applicationRuntimePolicy !== 'none' ? { application_runtime_policy: $.val(applicationRuntimePolicy) } : {}), scoped_administration: $.val(accessControls?.get('scopedAdministration')) .toBoolean() .def(false), can_edit_in_studio: $.val(accessControls?.get('canEditInStudio')).toBoolean().def(true), js_level: $.from('jsLevel').toString().def('es_latest'), restrict_table_access: $.val(accessControls?.get('restrictTableAccess')).toBoolean().def(false), runtime_access_tracking: $.map(() => { const ratShape = accessControls?.get('runtimeAccessTracking') const userProvidedRat = ratShape?.getValue() as string | undefined // ARP may require a specific RAT value const requiredRat = ARP_TO_RAT_MAP[applicationRuntimePolicy] // Error only when user *explicitly* set a conflicting value — not when it was omitted if (userProvidedRat && requiredRat && userProvidedRat !== requiredRat) { diagnostics.error( ratShape || config.get('accessControls'), `Runtime Access Tracking is set to '${userProvidedRat}' but Application Runtime Policy '${applicationRuntimePolicy}' requires '${requiredRat}'. ` + `Set runtimeAccessTracking to '${requiredRat}' to match the Application Runtime Policy.` ) } // Hint when ARP auto-derives RAT and user also set it explicitly (even if not conflicting) if (userProvidedRat && requiredRat && userProvidedRat === requiredRat) { diagnostics.hint( ratShape || config.get('accessControls'), `runtimeAccessTracking is auto-derived from applicationRuntimePolicy='${applicationRuntimePolicy}'. ` + `The explicit value '${userProvidedRat}' can be removed.` ) } // Auto-derive from ARP when it specifies a required value; otherwise fall back to // the user-provided value or the platform default of 'permissive'. const rat = requiredRat ?? userProvidedRat ?? 'permissive' // Convert 'none' to undefined for XML (empty field). // Round-trip safe: toShape maps empty string back to 'none' (line 796-798) if (rat === 'none') { return undefined } return rat }), licensable: $.val(licensing?.get('licensable')).toBoolean().def(true), enforce_license: $.val(licensing?.get('enforceLicense')).toString().def('log'), license_model: $.val(licensing?.get('licenseModel')).toString().def('none'), menu: $.toString().def(''), user_role: $.val(accessControls?.get('userRole')).toString().def(''), short_description: $.from('description').toString().def(''), logo: $.val(logoShape ?? logoId).def(''), guided_setup_guid: $.from('guidedSetupGuid').toString().def(''), subscription_entitlement: $.val(licensing?.get('subscriptionEntitlement')).toString().def(''), private: $.val(accessControls?.get('private')).toBoolean().def(false), trackable: $.val(accessControls?.get('trackable')).toBoolean().def(true), uninstall_blocked: $.val(accessControls?.get('uninstallBlocked')).toBoolean().def(false), hide_on_ui: $.val(accessControls?.get('hideOnUI')).toBoolean().def(false), installed_as_dependency: $.from('installedAsDependency').toBoolean().def(false), license_category: $.val(licensing?.get('licenseCategory')).toString().def('none'), scope: $.val(scope), package_json: $.val(packageJsonPath), name: $.val(name), source: $.val(scope), sys_id: $.from('scopeId'), version: $.val(version), package_resolver_version: $.from('packageResolverVersion').def( scope === 'global' ? MODULE_RESOLUTION.V2 : MODULE_RESOLUTION.V1 ), sys_code: $.from('sysCode'), })), }) const policyRecords: SdkRecord[] = [] const scopeId = config.get('scopeId').asString().getValue() // Create network policy records if (networkPoliciesShape.length > 0) { const networkRecords = await createNetworkPolicyRecords( networkPoliciesShape, factory, config, diagnostics ) policyRecords.push(...networkRecords) } // Create wildcard policy record if (wildcardPolicyShape) { const wildcardRecord = await createWildcardPolicyRecord( wildcardPolicyShape, factory, config, scopeId, diagnostics ) policyRecords.push(wildcardRecord) } // Create performance policy record if (performancePolicyShape) { const performanceRecord = await createPerformancePolicyRecord( performancePolicyShape, factory, config, scopeId, applicationRuntimePolicy, diagnostics ) policyRecords.push(performanceRecord) } return { success: true, value: sysAppRecord.with(...policyRecords), } }, }, ], })