Skip to content

poc: runtime resource override resolution for getByName#575

Draft
Raina451 wants to merge 2 commits into
mainfrom
poc/override-resolution-test
Draft

poc: runtime resource override resolution for getByName#575
Raina451 wants to merge 2 commits into
mainfrom
poc/override-resolution-test

Conversation

@Raina451

@Raina451 Raina451 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

POC for runtime resource override resolution in the SDK. Proves that admin-time overrides (injected into index.html at deploy time) can modify resource names used in API calls — even though the original names are hardcoded in the compiled JS bundle.

What this does

  • Adds resolveOverride() utility that reads <script id="uipath-overrides"> from the DOM at runtime
  • Integrates into FolderScopedService.getByNameLookup() — the shared method used by assets.getByName(), buckets.getByName(), processes.getByName()
  • The compiled code passes "CustomerConfig" → SDK looks up override → API call uses "ProdConfig" instead

How overrides flow

  1. Developer writes: assets.getByName(bindings.taggedAsset.name) → compiled to assets.getByName("CustomerConfig")
  2. At deploy time: syncResourceOverwritesToCdn injects <script id="uipath-overrides"> into index.html
  3. At runtime: SDK's getByNameLookup calls resolveOverride("asset", "CustomerConfig", "Shared/PublicApps")
  4. Override found → $filter=Name eq 'ProdConfig' sent to API (NOT "CustomerConfig")

POC validation

Tested with a Vite React app (npm run buildnpm run preview):

  • Verified "CustomerConfig" is hardcoded in the compiled JS bundle (grep confirms)
  • Verified override resolution reads from DOM at runtime and substitutes the name
  • Verified with real SDK calls against alpha.uipath.com / appsdev / appsdevDefault

Context

This is part of the Resource Access Policy (RAP) design for public coded apps. RAP needs to know the actual resource names sent in API calls to validate them at the Cloudflare Worker edge. The override resolution ensures the policy uses the same names the SDK sends.

Not production-ready — needs:

  • Cache invalidation when overrides change mid-session
  • Support for entity/bucket services beyond folder-scoped
  • Unit tests
  • Integration with the overrides team's delivery mechanism
  • Remove console.log statements

Generated with Claude Code

Raina451 and others added 2 commits July 2, 2026 15:57
Adds override resolution to FolderScopedService.getByNameLookup so that
admin-time resource overrides (injected into index.html as
<script id="uipath-overrides">) are applied at runtime before making
API calls.

When a developer calls assets.getByName("CustomerConfig"), the compiled
bundle has "CustomerConfig" hardcoded. At runtime, the SDK now reads the
override dict from the DOM. If an admin overrode "CustomerConfig" to
"ProdConfig", the $filter sent to the API uses "ProdConfig" instead.

This is a POC — production implementation needs:
- Cache invalidation when overrides change
- Support for entity/bucket services (not just folder-scoped)
- Integration with the overrides team's delivery mechanism
- Unit tests

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

try {
cachedOverrides = JSON.parse(el.textContent ?? '{}');
console.log('[UiPath SDK] Loaded overrides:', cachedOverrides);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.log calls must be removed from production code. The PR description lists "Remove console.log statements" as a TODO, but these statements are still present on lines 36, 39, and 62. They will emit SDK internals to the browser console for every app using this feature and should be removed before merging.

function loadOverrides(): OverrideDict {
if (loaded) return cachedOverrides ?? {};

loaded = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loaded = true is set before the element check at line 32. If this module initialises before <script id="uipath-overrides"> is in the DOM (e.g., a service is instantiated during app startup, before the injected script is parsed), loaded is permanently flipped to true and every subsequent call returns {}. The override system silently stops working for the entire session with no error.

Consider only setting loaded = true (and populating cachedOverrides) when the element is actually found and parsed successfully, or at minimum documenting that callers must not invoke any getByName* method until the DOM is fully loaded.

throw new NotFoundError({
message: `${resourceType} '${validatedName}' not found${folderHint}.`,
message: `${resourceType} '${resolvedName}' not found${folderHint}.`,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The not-found error message now exposes resolvedName (the admin-override value, e.g. "ProdConfig") to the developer. The developer wrote getByName("CustomerConfig") — they have no knowledge of "ProdConfig" and can't act on an error about it.

Consider including the original name to make the error actionable:

message: `${resourceType} '${validatedName}'${override ? ` (overridden to '${resolvedName}')` : ''} not found${folderHint}.`,

This way users with overrides configured get a diagnostic hint, while users without overrides see the same clean message as before.

@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review summary

Three issues found (all new — no prior threads to dedupe against):

1. Logging calls in production code (resolve-override.ts lines 36, 39, 62)
The SDK emits internal state to the browser console on every override load and resolution. The PR description lists removal as a TODO but they are still present. Must be removed before merging.

2. Silent permanent failure if module loads before DOM is ready (resolve-override.ts line 27)
loaded = true is set before checking whether the script element exists. If any service that calls getByName* is instantiated before the injected script tag is parsed, loaded is permanently set and all subsequent calls return {} — silently disabling overrides for the entire session with no error.

3. Not-found error exposes admin override name (folder-scoped.ts ~line 139)
When an override maps CustomerConfig to ProdConfig and ProdConfig is not found, the error reads Asset 'ProdConfig' not found. The developer who wrote getByName("CustomerConfig") has never heard of ProdConfig and cannot act on this. Suggest including both the original and resolved name in the message.

Comment on lines +21 to +22
let cachedOverrides: OverrideDict | null = null;
let loaded = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module-level mutable state will cause test pollution in vitest. Because vitest runs tests in the same process with a shared module registry, loaded and cachedOverrides persist across test files — once any test triggers loadOverrides(), the cached value is frozen for the rest of the suite unless vi.resetModules() is called explicitly.

Consider exporting a resetOverridesCache() function (for tests only, marked @internal) or refactoring to a factory/class so tests can create a fresh instance per test:

Suggested change
let cachedOverrides: OverrideDict | null = null;
let loaded = false;
export interface ResourceOverride {
name: string;
folderPath: string;
}
type OverrideDict = Record<string, ResourceOverride>;
let cachedOverrides: OverrideDict | null = null;
let loaded = false;
/** @internal — test helper only */
export function _resetOverridesCache(): void {
cachedOverrides = null;
loaded = false;
}

folderPath: string,
): ResourceOverride | null {
const overrides = loadOverrides();
const key = `${resourceType}.${name}.${folderPath}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dot-separated key format is ambiguous whenever a resource name or folder path contains a .. For example, an asset named "v2.0" in folder "Shared/Apps" produces asset.v2.0.Shared/Apps, which can't be unambiguously split back into its three parts.

Use a separator that cannot appear in resource names or folder paths, e.g. :::

Suggested change
const key = `${resourceType}.${name}.${folderPath}`;
const key = `${resourceType}::${name}::${folderPath}`;

The generation side (syncResourceOverwritesToCdn) must use the same separator.

const override: ResourceOverride | null = resolveOverride(
resourceType.toLowerCase(),
validatedName,
folderPath ?? '.'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using '.' as the fallback for an undefined folderPath creates a silent coupling with the override generation side — both sides must agree on the same sentinel value, or lookups will always miss when no explicit folder is passed. If the generation side uses a different default (empty string, undefined, or some other sentinel), overrides will silently not apply.

Either document the contract explicitly in the JSDoc of resolveOverride, or pass undefined / '' as-is and let the generation side handle the undefined case consistently:

Suggested change
folderPath ?? '.'
folderPath ?? ''

const folderHint = describeFolderForError(folderId, folderKey, resolvedFolderPath);
throw new NotFoundError({
message: `${resourceType} '${validatedName}' not found${folderHint}.`,
message: `${resourceType} '${resolvedName}' not found${folderHint}.`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using resolvedName in the error message makes debugging harder: a developer who wrote assets.getByName("CustomerConfig") will see Asset 'ProdConfig' not found in their logs and have no idea why, since "ProdConfig" never appears in their code.

When an override is active, include both names in the message:

Suggested change
message: `${resourceType} '${resolvedName}' not found${folderHint}.`,
message: override
? `${resourceType} '${validatedName}' (overridden to '${resolvedName}') not found${folderHint}.`
: `${resourceType} '${validatedName}' not found${folderHint}.`,

@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review notes

Four issues found (not duplicating the known TODOs already listed in the PR description):

  1. Module-level mutable state (resolve-override.ts lines 21-22) — loaded/cachedOverrides persist across vitest test runs in the same process, causing test pollution. Suggest exporting a _resetOverridesCache() helper or refactoring to a factory.

  2. Ambiguous dot key format (resolve-override.ts line 58) — dots in resource names or folder paths produce ambiguous keys (e.g. asset.v2.0.Shared/Apps). Use a separator like :: that cannot appear in those strings.

  3. Undefined folderPath sentinel (folder-scoped.ts line 106) — folderPath falling back to '.' silently couples the SDK to the generation side. If the two sides drift, overrides silently stop applying. Document the contract or use a consistent empty-string default.

  4. Error message shows override name, not original (folder-scoped.ts line 138) — a developer who wrote assets.getByName("CustomerConfig") will see "Asset 'ProdConfig' not found" with no indication an override is involved. Include both names when an override is active.

@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
69.6% Coverage on New Code (required ≥ 90%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant