Skip to content

feat(apollo-react): centralized guardrails section and read-only details [AL-578] - #1161

Draft
andreizdrali-uipath wants to merge 2 commits into
mainfrom
feat/apollo-react-guardrail-centralized-section
Draft

andreizdrali-uipath wants to merge 2 commits into
mainfrom
feat/apollo-react-guardrail-centralized-section

Conversation

@andreizdrali-uipath

@andreizdrali-uipath andreizdrali-uipath commented Sep 14, 2026

Copy link
Copy Markdown

Implements AL-578: the read-only section for the
guardrails an organization's AI Trust Layer governance policy enforces on an agent, and the details
content behind a row. Both products ship this today, each with its own copy and its own component
tree. One commit, stacked on #1139.

What lands

Module Public surface
centralized-types.ts CentralizedGuardrail and the five shapes around it
centralized-guardrail-utils.ts filtering, definition matching, display resolution, resolveCentralizedGuardrailParameters, three label formatters. Pure, React-free
centralized-guardrails-section.tsx CentralizedGuardrailsSection
centralized-guardrail-details.tsx CentralizedGuardrailDetails
i18n.ts CentralizedGuardrailsLabels, CENTRALIZED_GUARDRAILS_EN_LABELS, CENTRALIZED_GUARDRAILS_EN_MESSAGES, resolveCentralizedGuardrailsLabels, useCentralizedGuardrailsLabels
policy.centralizedGuardrails → getApplicableCentralizedGuardrails → CentralizedGuardrailsSection
                                       (host filters)                  onSelect(guardrail)
                                                                              ↓
                                                     host dialog / panel ⊃ CentralizedGuardrailDetails

Plus four private components (row, origin chip, parameters, detail field), a README section,
Components/UiPath/Centralized Guardrails stories including both host shells, and the
guardrails.centralized.* catalog ids.

Decisions worth checking

  • A governance record is its own type, not a variant of GuardrailBuilderValue. No id,
    scopes at the top level rather than under a selector, action a bare discriminator.
    centralized-types.ts mirrors both products' zod schemas structurally, so each host passes its
    own inferred type with no mapping: executionStage stays string because both parse it as one,
    and action is the closed four-value union both close it to, which a TypeScript string enum
    member assigns to, so Agents' ActionType fits.
  • Props, never contexts. Both products hold the policy and the definitions in a context of
    their own (useAiTrustLayerGovernancePolicy, useGovernance, GuardrailDefinitionsContext), so
    reading one here would tie the component to whichever host it was written in.
  • The host filters, the component renders. getApplicableCentralizedGuardrails is the
    agent-kind predicate both products already run, exported so neither rewrites it. An empty list
    renders nothing, which is what both do; emptyState overrides that.
  • definitions is optional and undefined means "not loaded yet", which is what keeps a row
    from claiming a configuration was deleted while the catalog is still in flight. An empty array
    means it loaded and the configuration really is gone.
  • One configuration resolver for both origins. A BYO guardrail states its configuration as
    connector parameters and a built-in as entities / entityThresholds.
    resolveCentralizedGuardrailParameters lifts the built-in fields onto the parameter shape so one
    path covers both, and it discriminates on each value's own shape rather than on parameterType,
    which is an unvalidated wire string.
  • A read-only value is text, not a disabled input. The family's parameter editors are the
    MetadataForm stack and have no read-only mode, and these values arrive as untyped wire data
    rather than as GuardrailValidatorParameters. A disabled input also cannot be focused, so its
    content is not selectable, not copyable and skipped by a screen reader.
  • The row's accessible name is its own text, not an aria-label.
  • mt-0 on the two title-less alerts. Wind's AlertDescription is unconditionally mt-1,
    which assumes an AlertTitle above it, while alertVariants pins the icon at top-3.5, so a
    title-less alert renders its text 4px below its icon. The real fix is one line in
    alertVariants; this is the local workaround until that lands.

QA-visible changes

Both products

  • Entities render through the definition's option labels: "US Social Security Number (SSN)" instead
    of USSocialSecurityNumber, matching the guardrail editor next to it.
  • Each validator's configuration is labelled from its own definition ("Content categories" /
    "Severity thresholds" for harmful content, "Detection thresholds" for PII) instead of one generic
    pair, which stays as the fallback when no definition matched.
  • A broken BYO configuration gains a short status chip (Configuration missing / Configuration disabled) next to the name. The remediation sentence both products already show stays
    underneath: the chip is for finding the row, the sentence for fixing it.
  • The row's accessible name becomes its own text. Both products set
    aria-label="View details for X" today, which overrides the content and hides the description,
    the provider, the scopes and the broken-configuration message from screen readers.

Agents (frontend-sw)

  • Restyles from MUI to wind: the details go from a dialog with tables to a definition list. The
    dialog itself stays Agents', since the component is content, not a shell.
  • The section is a card; unstyled + hideHeader keep the existing SectionAccordion chrome.
  • Copy: info and managedMessage take Flow's wording, docsLink becomes "View centralized
    guardrails documentation".

Flow (flow-workbench)

  • Read-only values render as text rows rather than greyed, disabled inputs.
  • A row with no panel overlay is plain text rather than a disabled button.
  • description reuses the builder's "Guardrail description" label; thresholdsFallback keeps
    Flow's plural wording.
  • The validator === 'harmful_content' threshold-label branch is replaced by the definition-driven
    label.

Copy and localization

English harvested from both products, in one builder function with the rest of the family. Where
the two agree this package says the same thing; the five places they disagree are declared with
a reason in centralized-parity.test.ts and asserted against both transcriptions, so the suite
fails on an undeclared difference, a stale declaration, or a third wording we invented.

Scope, action, type and description labels reuse the builder's own ids rather than declaring
guardrails.centralized.* twins, so those strings are already translated in all thirteen locales
and cannot drift from the editor's. English only for the ids this block declares itself, like
the rest of the package: chore(l10n): sync from Localization owns the other catalogs.
useSafeLingui renders the English default until it runs, so nothing is blank.

Verification

tsc clean, biome lint and format clean, rslib build clean. Guardrails directory:
24 files, 460 tests passing, with jest-axe on both rendering suites. biome flags four
pre-existing warnings in guardrail-builder.tsx and use-metadata-form-bridge.ts, which arrived
with #1138 and are untouched here.

Review questions

  1. The status chip is an addition. Both products show only the remediation sentence. Keeping
    both is deliberate redundancy on an error path; if you would rather the row stayed at parity,
    removing the chip is one &&.
  2. Reusing the builder's ids for scopes, actions and two field labels saves ten duplicate
    strings and keeps them from drifting, but it does couple the two label blocks.
  3. findCentralizedBuiltInDefinition is new. Neither product looks a built-in definition up
    today; it exists only so the configuration rows can be labelled and the entities named. It never
    feeds the display name or description, because a policy can enforce a validator the tenant has
    no definition for.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings September 14, 2026 10:14
@andreizdrali-uipath andreizdrali-uipath added the dev-packages Adds dev package publishing on pushes to this PR label Sep 14, 2026
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Apollo Coded App preview deployments are ready.

Project Status Preview Updated (PT)
apollo-design Ready Preview · Logs Sep 17, 2026, 07:54:04 AM
apollo-docs Ready Preview · Logs Sep 17, 2026, 07:54:04 AM
apollo-landing Ready Preview · Logs Sep 17, 2026, 07:54:04 AM
apollo-vertex Ready Preview · Logs Sep 17, 2026, 07:54:04 AM

@github-actions

Copy link
Copy Markdown
Contributor

Dependency License Review

  • 1937 package(s) scanned
  • ✅ No license issues found
  • ⚠️ 2 package(s) excluded (see details below)
License distribution
License Packages
MIT 1708
ISC 88
Apache-2.0 55
BSD-3-Clause 27
BSD-2-Clause 23
BlueOak-1.0.0 8
MPL-2.0 4
MIT-0 3
CC0-1.0 3
MIT OR Apache-2.0 2
(MIT OR Apache-2.0) 2
Unlicense 2
LGPL-3.0-or-later 1
Python-2.0 1
CC-BY-4.0 1
(MPL-2.0 OR Apache-2.0) 1
Unknown 1
Artistic-2.0 1
(WTFPL OR MIT) 1
(BSD-2-Clause OR MIT OR Apache-2.0) 1
CC-BY-3.0 1
0BSD 1
(MIT OR CC0-1.0) 1
MIT AND ISC 1
Excluded packages
Package Version License Reason
@img/sharp-libvips-linux-x64 1.3.2 LGPL-3.0-or-later LGPL pre-built binary, not linked
khroma 2.1.0 Unknown MIT per GitHub repo, missing license field in package.json

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

📦 Dev Packages

Package Status Updated (PT)
@uipath/apollo-react@6.47.4-pr1161.5c450d6 🟢 Published Sep 17, 2026, 07:51:44 AM

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Resolve the critical key-collision issue and the two moderate UI rendering issues.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds shared read-only centralized guardrails list and details UI for Apollo React, with definition-aware rendering, localization, status messaging, and host-controlled shells.

Changes:

  • Adds centralized guardrail types, utilities, components, and exports.
  • Adds read-only sections, details, configuration displays, stories, and documentation.
  • Updates form integrations, chat rendering, localization catalogs, tests, and dependencies.

Review findings: Critical (2 votes): guardrail key collisions in centralized-guardrail-utils.ts. Moderate (1 vote each): interactive tooltip nesting in parameter-label.tsx, and unstable renderer identities in chat-message-content.tsx.

File summaries
File Reviewed change
pnpm-lock.yaml Dependency lockfile updates
packages/apollo-wind/src/index.ts Public package exports
packages/apollo-wind/src/components/ui/textarea.tsx Textarea component
packages/apollo-wind/src/components/ui/select.tsx Select component
packages/apollo-wind/src/components/ui/info-tooltip.tsx Info tooltip component
packages/apollo-wind/src/components/ui/info-tooltip.test.tsx Tooltip tests
packages/apollo-wind/src/components/ui/info-tooltip.stories.tsx Tooltip stories
packages/apollo-wind/src/components/ui/index.ts UI exports
packages/apollo-wind/src/components/ui/form-field.tsx Form field layout and labels
packages/apollo-wind/src/components/ui/form-field.test.tsx Form field tests
packages/apollo-wind/src/components/forms/validation-converter.ts Validation conversion
packages/apollo-wind/src/components/forms/validation-converter.test.ts Validation tests
packages/apollo-wind/src/components/forms/string-list-field.tsx String-list field
packages/apollo-wind/src/components/forms/schema-serializer.ts Schema serialization
packages/apollo-wind/src/components/forms/schema-serializer.test.ts Serialization tests
packages/apollo-wind/src/components/forms/rules-engine.ts Form rules engine
packages/apollo-wind/src/components/forms/README.md Forms documentation
packages/apollo-wind/src/components/forms/metadata-form.stories.tsx Metadata form stories
packages/apollo-wind/src/components/forms/index.ts Forms exports
packages/apollo-wind/src/components/forms/form-schema.ts Form schema definitions
packages/apollo-wind/src/components/forms/form-designer.tsx Form designer
packages/apollo-wind/src/components/forms/demo-mocks.ts Demo form mocks
packages/apollo-wind/src/components/forms/custom-controls.stories.tsx Custom control stories
packages/apollo-react/src/test/setup.ts Test setup
packages/apollo-react/src/material/components/ap-chat/components/message/chat-message-content.tsx Locale-aware chat rendering
packages/apollo-react/src/material/components/ap-chat/components/message/chat-message-content.test.tsx Chat rendering tests
packages/apollo-react/src/i18n/index.ts Internationalization exports
packages/apollo-react/src/canvas/locales/ru.json Russian translations
packages/apollo-react/src/canvas/components/index.ts Canvas component exports
packages/apollo-react/src/canvas/components/Guardrails/utils.ts Guardrail utilities
packages/apollo-react/src/canvas/components/Guardrails/use-metadata-form-bridge.ts Metadata form bridge
packages/apollo-react/src/canvas/components/Guardrails/types.ts Guardrail types
packages/apollo-react/src/canvas/components/Guardrails/render-parameter-bridge.tsx Parameter rendering bridge
packages/apollo-react/src/canvas/components/Guardrails/index.ts Guardrail exports
packages/apollo-react/src/canvas/components/Guardrails/i18n.test.ts Localization tests
packages/apollo-react/src/canvas/components/Guardrails/guardrail-form-layout.tsx Guardrail form layout
packages/apollo-react/src/canvas/components/Guardrails/guardrail-form-layout.test.tsx Layout tests
packages/apollo-react/src/canvas/components/Guardrails/guardrail-form-layout.stories.tsx Layout stories
packages/apollo-react/src/canvas/components/Guardrails/definitions-wire.ts Wire definitions
packages/apollo-react/src/canvas/components/Guardrails/definitions-copy.test.ts Definition copy tests
packages/apollo-react/src/canvas/components/Guardrails/components/parameter-label.tsx Parameter labels and tooltips
packages/apollo-react/src/canvas/components/Guardrails/components/mixed-scopes-banner.tsx Mixed-scopes banner
packages/apollo-react/src/canvas/components/Guardrails/components/mixed-scopes-banner.test.tsx Banner tests
packages/apollo-react/src/canvas/components/Guardrails/components/map-enum-field.tsx Enum field mapping
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-chip.tsx Status chip
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-chip.test.tsx Status chip tests
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-banner.tsx Status banner
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-banner.test.tsx Status banner tests
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-scope-selector.tsx Scope selector
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-chip.tsx Guardrail chip
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-chip.test.tsx Guardrail chip tests
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-action-section.tsx Action section
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-action-section.test.tsx Action section tests
packages/apollo-react/src/canvas/components/Guardrails/components/field-shell.tsx Field shell
packages/apollo-react/src/canvas/components/Guardrails/components/field-shell.test.tsx Field shell tests
packages/apollo-react/src/canvas/components/Guardrails/components/enum-list-chips-field.tsx Enum list field
packages/apollo-react/src/canvas/components/Guardrails/components/centralized-guardrail-row.tsx Centralized guardrail rows
packages/apollo-react/src/canvas/components/Guardrails/components/centralized-guardrail-parameters.tsx Read-only parameters
packages/apollo-react/src/canvas/components/Guardrails/components/centralized-guardrail-origin-chip.tsx Origin indicator
packages/apollo-react/src/canvas/components/Guardrails/centralized-types.ts Centralized guardrail contracts
packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrails-section.tsx Read-only guardrail section
packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-details.tsx Guardrail details
packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-details.test.tsx Details and accessibility tests
packages/apollo-react/src/canvas/components/Guardrails/builder-utils.ts Builder utilities
packages/apollo-react/src/canvas/components/Guardrails/builder-types.ts Builder types
packages/apollo-react/src/canvas/components/Guardrails/__fixtures__/definitions-wire.fixtures.ts Definition fixtures
packages/apollo-react/src/canvas/components/Guardrails/__fixtures__/catalog-coverage.ts Catalog coverage fixture
packages/apollo-react/package.json Package metadata and scripts
Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (2)

packages/apollo-react/src/canvas/components/Guardrails/components/parameter-label.tsx:35

  • InfoTooltip renders a real <button>, so placing it inside Label creates an interactive labelable descendant and makes clicks on the tooltip ambiguously activate the associated form control. This also contradicts the shared FormFieldLabel pattern, which deliberately renders the tooltip trigger beside the label (packages/apollo-wind/src/components/ui/form-field.tsx:61-69). Keep the tooltip outside the <Label> for the normal field-label path, while retaining the current text-header path.
    packages/apollo-react/src/material/components/ap-chat/components/message/chat-message-content.tsx:35
  • getApolloMessageRenderers creates new arrow-function component types on every AutopilotChatMessageContentComponent render. Because the result is used as <ApolloMessageRenderer>, any parent rerender changes the element type and unmounts/remounts the tool-call or tree renderer, resetting local state such as an expanded tool call and losing its DOM state. Define these renderer components at module scope (passing locale as a prop) or memoize a stable renderer table instead of creating component identities during render.
  • Files reviewed: 112/113 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +46 to +47
const origin = guardrail.isByo ? (guardrail.name ?? 'byo') : 'builtin';
return `${guardrail.validator}-${guardrail.executionStage}-${origin}`;
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage + size by package

Per-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.

Package Coverage New-line coverage Packed (gzip) Unpacked vs main
@uipath/apollo-core 42.01 MB 50.16 MB ±0
@uipath/apollo-react 45.7% 91.5% (377/412) 7.71 MB 29.92 MB +45.6 KB
@uipath/apollo-ui-icons 2.86 MB 6.96 MB ±0
@uipath/apollo-wind 464.6 KB 2.93 MB −13 B
@uipath/ap-chat 85.8% 43.94 MB 56.79 MB +1.8 KB

"Coverage" is each package's own coverage.include scope (e.g. apollo-core instruments only scripts/). "Packed"/"Unpacked" come from npm pack --dry-run and only cover built packages — "—" means not measured this run (package not affected / not built). "vs main" is the packed (gzipped) delta against the last successful main build (the package-sizes artifact from the Release workflow); "—" there means no main baseline was available this run. The baseline is main's latest build, not this PR's exact merge-base, so it includes any drift since the branch diverged. Packages with no vitest config are omitted.

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Storybook visual diff

⚠️ Visual changes detected: 17 changed, 11 added (of 407 compared, 379 unchanged). View report

Baseline is the deployed main Storybook, so changes merged to main after this branch was last updated can also appear here. Logs

Updated (PT): Sep 17, 2026, 08:12:59 AM

Copilot AI review requested due to automatic review settings September 14, 2026 12:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Four moderate review findings remain unresolved, covering threshold-map handling, decimal input editing, label/tooltip structure, and chat renderer identity.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (4)

Previously missed (2) — in code that hasn't changed since the last review.

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:257

  • The has check treats a parameter entry whose value is null/undefined as configured, so the keySource row is consumed even though toThresholdRow has no threshold value to render. A connector payload with { id: 'thresholds', value: undefined } (or null) will therefore hide the selected entities entirely. Check that the value is a plain threshold-map object before adding its keySource to consumedIds (and add a regression test for an explicit empty value).
    packages/apollo-react/src/material/components/ap-chat/components/message/chat-message-content.tsx:210
  • getApolloMessageRenderers creates new inline component functions on every render, so the ApolloMessageRenderer type changes whenever this component re-renders. React consequently remounts the tool/tree renderer and loses state such as an expanded ApToolCall; the previously existing test covered this behavior but is removed in this change. Memoize the renderer list by locale (unconditionally, before the early returns) or move these renderer components to module scope so their identity is stable.

packages/apollo-react/src/canvas/components/Guardrails/components/map-enum-field.tsx:74

  • Clearing or editing a decimal threshold can be coerced to 0 before the user finishes typing: an empty intermediate value and values such as 0. both pass through Number.parseFloat(...) || 0, immediately replacing the controlled input. This makes ordinary keyboard editing of fractional thresholds unreliable and can persist an unintended zero; retain the in-progress string (or otherwise defer numeric coercion until a complete value is available) and convert it at commit/validation time.
    packages/apollo-react/src/canvas/components/Guardrails/components/parameter-label.tsx:25
  • InfoTooltip renders a real <button>, so placing it inside the <label> creates invalid label content and makes clicking the tooltip trigger ambiguously activate the associated control. FormFieldLabel already avoids this by rendering the label and tooltip as siblings; use the same wrapper pattern here while keeping the tooltip inside the text-header branch where no label is rendered.
  • Files reviewed: 112/113 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 14, 2026 13:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Three moderate findings remain unresolved around parameter handling, row-key collisions, and the info-button accessible label.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (5)

Previously missed (2) — in code that hasn't changed since the last review.

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:261

  • valuesById.has(...) only checks that the parameter entry exists, so a map-enum whose value is explicitly null or undefined still consumes its keySource list. That makes a selected entity list render as a threshold row with unset values instead of remaining a standalone value row, contrary to the documented “only when it has a value” rule. Check the stored map value for null/undefined before adding the source id to consumedIds.
    packages/apollo-react/src/canvas/components/Guardrails/components/centralized-guardrail-row.tsx:120
  • This comment says the no-handler path renders a disabled button, but the branch immediately below returns a <div> specifically so the row is plain text and not a control. Please update the comment to describe the current behavior; otherwise future changes may incorrectly restore the disabled-button behavior the story and tests reject.

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:47

  • executionStage is intentionally an open string and both validator and a BYO name can contain -, so concatenating these fields with - is not collision-safe. For example, a built-in { validator: 'a-b', executionStage: 'c' } and { validator: 'a', executionStage: 'b-c' } produce the same React key, which can trigger duplicate-key warnings and make React reuse the wrong row after a policy update. Encode the validator, stage, origin kind, and BYO name as separate tuple fields instead of delimiter concatenation.
  const origin = guardrail.isByo ? (guardrail.name ?? 'byo') : 'builtin';
  return `${guardrail.validator}-${guardrail.executionStage}-${origin}`;

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrails-section.tsx:114

  • This icon-only button uses the full popover body as its accessible name, so a screen reader announces the long policy sentence as the control name instead of identifying the control as “More information.” The wind FormFieldLabel pattern keeps these separate (content versus tooltipAriaLabel); add a localized centralized info-button label and use it for aria-label here while leaving labels.info as the popover content.
                  aria-label={labels.info}

packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-chip.tsx:17

  • The new success tone is not exercised by the existing parameterized test, which only covers neutral, warning, and error. Since this mapping selects a distinct badgeVariants class, a regression could make the BYO origin/status chip use the wrong styling while the suite remains green; add a success case (and include it in the relevant accessibility render if desired).
  • Files reviewed: 112/113 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

andreizdrali-uipath added a commit that referenced this pull request Sep 14, 2026
The legacy entries the list replaces highlight on hover, which is what makes
a clickable row readable as clickable. The tint sits on the row container
rather than on the body that carries `role="button"`, because the handle and
the actions are siblings of it and legacy highlights the entire entry; the
negative margin pairs with the padding so the tinted area extends past the
content without moving it.

Gated on `rowActivatesEdit`, so a row that does nothing when clicked does not
advertise that it does. Same classes as the centralized section's row in
#1161, so the two families hover alike.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andreizdrali-uipath added a commit that referenced this pull request Sep 14, 2026
The tint landed gated on `rowActivatesEdit`, which neither host passes yet,
so in practice no row ever highlighted. A row is a hover target whether or
not clicking it opens the editor: it is what you aim the drag handle and the
row actions at, and the centralized section's rows in #1161 highlight the
same way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 14, 2026 14:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (4)

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:47

  • This key is built by joining unconstrained wire strings with -, so distinct (validator, executionStage, name) tuples can collide (for example a-b + c and a + b-c). If both rows are present, React can reuse the wrong row DOM/state. Encode the tuple rather than using an ambiguous delimiter.
  return `${guardrail.validator}-${guardrail.executionStage}-${origin}`;

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:257

  • valuesById.has() is also true when the connector sends a map parameter with value: null or value: undefined. That makes this consume the keySource list and toThresholdRow renders selected entities with , whereas an unset map should leave the entity-list row visible (the same behavior as when the map parameter is absent). Check that the map value is non-null/defined before adding its key source to consumedIds.
      parameterDefinition.keySource !== undefined &&
      valuesById.has(parameterDefinition.id) &&
      rendersAsThresholds(parameterDefinition.id)

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrails-section.tsx:52

  • When a translation or labels.policyCaption omits {{policyName}}, split returns only the full template and after is undefined, so the emphasized policy name is rendered nowhere even though the comment says it is appended. This can hide which governance policy is enforcing the section; preserve all token occurrences and append the name as a fallback when the token is absent.
  const [before, after] = template.split('{{policyName}}');

packages/apollo-react/src/material/components/ap-chat/components/message/chat-message-content.tsx:38

  • getApolloMessageRenderers now creates new inline component functions on every render. Because the selected function is rendered as <ApolloMessageRenderer>, any parent/context/message update changes its React type and remounts tool-call/tree content, resetting local state such as an expanded ApToolCall. Keep these renderer component identities stable, for example by memoizing the list by locale at a hook-safe location or by moving the wrappers to module scope.
  • Files reviewed: 112/113 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 14, 2026 14:24
andreizdrali-uipath added a commit that referenced this pull request Sep 14, 2026
Both branches add the same README section, so make the two copies byte for
byte identical and the merge takes one of them instead of conflicting on the
wording.

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Two moderate correctness issues remain in centralized guardrail handling, and the stale accessibility comment should be updated.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:276

  • An empty threshold map ({} or null) should leave its keySource list as a normal value row, but this presence check consumes that list and toThresholdRow unconditionally imports the selected keys into an all-unset threshold table. This contradicts the comment above that only a map with a value should absorb the list; gate both consumption and key-source import on at least one numeric map entry.

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:46

  • The key does not encode the origin type, so a valid BYO configuration named builtin collides with the built-in row for the same validator and execution stage (...-builtin). Policies can contain both origins for one validator, so React will warn about duplicate keys and reuse one row's state instead of rendering both. Prefix the BYO portion (for example, byo:${guardrail.name ?? 'byo'}) before composing the key.
  const origin = guardrail.isByo ? (guardrail.name ?? 'byo') : 'builtin';

packages/apollo-react/src/canvas/components/Guardrails/components/centralized-guardrail-row.tsx:123

  • This comment describes the old disabled-button behavior, but the implementation immediately below intentionally returns a plain <div> when there is no onSelect (and the story documents that behavior). The contradiction can mislead future changes about the accessibility contract; update the comment to explain that non-interactive rows are plain content and are not announced as unavailable.
  // A row with nowhere to go is not a control. Both products render a disabled button there,
  // which takes it out of the tab order anyway and announces it as unavailable rather than as
  // the plain text it actually is.
  • Files reviewed: 112/113 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

andreizdrali-uipath added a commit that referenced this pull request Sep 14, 2026
…AL-574]

The centralized section (#1161) needs the green both products already give the
BYO origin chip, and had forked its own copy of the chip to get it. The tone
belongs where the chip lives, so every consumer gets the same four.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@andreizdrali-uipath
andreizdrali-uipath force-pushed the feat/apollo-react-guardrail-centralized-section branch from 5421981 to dc434cb Compare September 14, 2026 15:22
Copilot AI review requested due to automatic review settings September 14, 2026 15:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Three moderate findings remain unresolved.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (3)

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:46

  • This key is not guaranteed to distinguish a built-in row from a BYO row: a valid BYO name of builtin produces the same validator-stage-builtin key as the built-in with that validator and stage. Since the component explicitly supports both origins sharing a validator, React can reuse the wrong row identity when both are present. Prefix the origin value (for example byo:${guardrail.name ?? ''} versus builtin) so the segments cannot collide.
  const origin = guardrail.isByo ? (guardrail.name ?? 'byo') : 'builtin';

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:257

  • This checks only whether the map parameter has an entry, not whether its value is present. A BYO payload such as { id: 'thresholds', value: null } alongside a selected keySource list therefore consumes the list and renders a threshold table of unset values, instead of retaining the selected list row. The comment above says the key source should be absorbed only when the map has a value; gate this on a non-null plain object (and add a regression test for a null/undefined map value).
  for (const parameterDefinition of definitions) {
    if (
      parameterDefinition.keySource !== undefined &&
      valuesById.has(parameterDefinition.id) &&
      rendersAsThresholds(parameterDefinition.id)

packages/apollo-react/src/canvas/components/Guardrails/components/centralized-guardrail-row.tsx:55

  • sr-only only hides this span visually; its text still participates in the button's accessible name along with the visible guardrail name and metadata. Screen readers will therefore announce a duplicated phrase such as “View details for PII detection PII detection …”. The native button role already conveys the action, so remove this child text or expose the action through separate description semantics instead of concatenating it into the name.
      {onSelect && (
        <span className="sr-only">{formatGuardrailFormMessage(labels.viewDetails, { name })}</span>
      )}
  • Files reviewed: 111/112 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

andreizdrali-uipath added a commit that referenced this pull request Sep 15, 2026
…AL-574]

The centralized section (#1161) needs the green both products already give the
BYO origin chip, and had forked its own copy of the chip to get it. The tone
belongs where the chip lives, so every consumer gets the same four.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andreizdrali-uipath added a commit that referenced this pull request Sep 15, 2026
…n [AL-579]

Wind's `AlertDescription` is unconditionally `mt-1`, which assumes an
`AlertTitle` sits above it, while `alertVariants` pins the icon at
`[&>svg]:top-3.5`. The app-picker-unavailable alert has no title, so its text
rendered 4px below the icon.

Same `mt-0` call-site workaround as `1b6792db` on #1161, `12a2d420` on #1140 and
`e7e12307` on #1147. This file was left out of that round because it belonged to
no open ticket then; AL-579 owns it now. The real fix is one line in wind's
`alertVariants`, which Andrei is opening separately, and which would let all four
workarounds be deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andreizdrali-uipath added a commit that referenced this pull request Sep 16, 2026
…AL-574]

The centralized section (#1161) needs the green both products already give the
BYO origin chip, and had forked its own copy of the chip to get it. The tone
belongs where the chip lives, so every consumer gets the same four.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andreizdrali-uipath added a commit that referenced this pull request Sep 16, 2026
…L-574]

A read-only status label for a guardrail row: definition status in the palette,
governance origin in the centralized section. It lands here rather than in each
leaf because #1140, #1147 and #1161 all branch from this layer and were each
carrying a byte-identical copy, kept in step by hand.

Deliberately not `GuardrailChip`, which wraps a Radix `Toggle`: these are
labels, and rendering them as toggles would put fake buttons in the tab order.
It is a `<span>` composed from wind's exported `badgeVariants` rather than the
`Badge` component, which renders a `<div>`, because the palette entry puts these
inside its `<button>` where flow content is invalid.

`GUARDRAIL_CHIP_GEOMETRY` is extracted from `guardrail-chip.tsx` so the
interactive and read-only pills stay one system rather than drifting apart.

Four tones, one per definition status plus the green both products already give
a bring-your-own origin. Nothing in this PR renders it yet; the three leaves do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andreizdrali-uipath added a commit that referenced this pull request Sep 16, 2026
…ilDefinitions [AL-574]

Turns the `GET /api/execution/guardrails/definitions` payload into the
`GuardrailDefinition`s `GuardrailBuilder` renders. Flow and Agents each carry
their own copy of this today, and the two have drifted.

- `definitions-wire.ts` mirrors the payload by hand, admitting both products'
  nullability variants, and reuses `GuardrailScope`/`GuardrailDefinitionStatus`
  so wire and display cannot drift.
- `definitions-parse.ts` validates with zod and never throws: a non-array sets
  `inputError`, one bad definition is dropped whole into `invalid`. Blank
  display strings are the single normalization, since one would beat curated
  copy and render an empty label; blank identifiers fail the entry instead of
  silently changing a definition's identity. zod stays private, pinned to the
  hand-written mirror by a bidirectional assignability check on the hot path
  plus a key-set test and a source-level import guard, so no schema type
  reaches the emitted `.d.ts`.
- `definitions-copy.ts` carries the six built-in validators' display copy as 63
  lingui messages in the shared canvas catalog, replacing Agents'
  `OOB_GUARDRAILS_I8N` and Flow's `buildValidatorDisplayInfo`. Ids use raw wire
  values, never a transcribed slug, which is how the two products ended up
  keying the same entity as `finNationalId` and `fiNationalId`. English only:
  the l10n sync owns the other catalogs, as it does for every other string here.
- `definitions-enrich.ts` resolves copy onto validated wire definitions. Pure
  and React-free, so Flow's vsix bridge calls it directly. Curated wins at
  definition level, wire wins at parameter level, BYO takes no curated copy.
- `use-guardrail-definitions.ts` composes the three. `options.definitions`
  skips the request entirely, so each product keeps its own transport. The
  context is compared by content rather than identity, unlike
  `useDiscoveryModels`, where an inline object refetches every render and never
  settles. Results carry the request key that produced them, so a tenant switch
  cannot keep serving the previous tenant's guardrails.

The 17 places the two products' English differs are each declared with a reason
in `definitions-parity.test.ts` and asserted against both products' transcribed
copy, so the suite fails on an undeclared difference or a wording we invented.

Also lands two things the leaf PRs were each carrying their own copy of, since
#1140, #1147 and #1161 all branch from here. `GuardrailStatusChip` is a
read-only pill for a guardrail row: deliberately not `GuardrailChip`, which
wraps a Radix `Toggle` and would put fake buttons in the tab order, and a
`<span>` composed from wind's `badgeVariants` rather than `Badge`, which renders
a `<div>` that is invalid inside the palette entry's `<button>`.
`GUARDRAIL_CHIP_GEOMETRY` is extracted so the interactive and read-only pills
stay one system. `__fixtures__/catalog-coverage.ts` holds the catalog scans that
stand in for `lingui extract`, which never sees `src/canvas` because it uses no
macros.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andreizdrali-uipath added a commit that referenced this pull request Sep 16, 2026
…ilDefinitions [AL-574]

Turns the `GET /api/execution/guardrails/definitions` payload into the
`GuardrailDefinition`s `GuardrailBuilder` renders. Flow and Agents each carry
their own copy of this today, and the two have drifted.

- `definitions-wire.ts` mirrors the payload by hand, admitting both products'
  nullability variants, and reuses `GuardrailScope`/`GuardrailDefinitionStatus`
  so wire and display cannot drift.
- `definitions-parse.ts` validates with zod and never throws: a non-array sets
  `inputError`, one bad definition is dropped whole into `invalid`. Blank
  display strings are the single normalization, since one would beat curated
  copy and render an empty label; blank identifiers fail the entry instead of
  silently changing a definition's identity. zod stays private, pinned to the
  hand-written mirror by a bidirectional assignability check on the hot path
  plus a key-set test and a source-level import guard, so no schema type
  reaches the emitted `.d.ts`.
- `definitions-copy.ts` carries the six built-in validators' display copy as 63
  lingui messages in the shared canvas catalog, replacing Agents'
  `OOB_GUARDRAILS_I8N` and Flow's `buildValidatorDisplayInfo`. Ids use raw wire
  values, never a transcribed slug, which is how the two products ended up
  keying the same entity as `finNationalId` and `fiNationalId`. English only:
  the l10n sync owns the other catalogs, as it does for every other string here.
- `definitions-enrich.ts` resolves copy onto validated wire definitions. Pure
  and React-free, so Flow's vsix bridge calls it directly. Curated wins at
  definition level, wire wins at parameter level, BYO takes no curated copy.
- `use-guardrail-definitions.ts` composes the three. `options.definitions`
  skips the request entirely, so each product keeps its own transport. The
  context is compared by content rather than identity, unlike
  `useDiscoveryModels`, where an inline object refetches every render and never
  settles. Results carry the request key that produced them, so a tenant switch
  cannot keep serving the previous tenant's guardrails.

The 17 places the two products' English differs are each declared with a reason
in `definitions-parity.test.ts` and asserted against both products' transcribed
copy, so the suite fails on an undeclared difference or a wording we invented.

Also lands two things the leaf PRs were each carrying their own copy of, since
#1140, #1147 and #1161 all branch from here. `GuardrailStatusChip` is a
read-only pill for a guardrail row: deliberately not `GuardrailChip`, which
wraps a Radix `Toggle` and would put fake buttons in the tab order, and a
`<span>` composed from wind's `badgeVariants` rather than `Badge`, which renders
a `<div>` that is invalid inside the palette entry's `<button>`.
`GUARDRAIL_CHIP_GEOMETRY` is extracted so the interactive and read-only pills
stay one system. Its tones are harvested, not invented: `success` is the green
both products give a BYO origin or connector chip, `info` the blue both give a
"Preview" lifecycle label (Flow `bg-blue-100 text-blue-700`, Agents
`semantic.colorInfoBackground`), so neither product's colour changes when the
leaves adopt it. `__fixtures__/catalog-coverage.ts` holds the catalog scans that
stand in for `lingui extract`, which never sees `src/canvas` because it uses no
macros.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andreizdrali-uipath added a commit that referenced this pull request Sep 16, 2026
…ilDefinitions [AL-574]

Turns the `GET /api/execution/guardrails/definitions` payload into the
`GuardrailDefinition`s `GuardrailBuilder` renders. Flow and Agents each carry
their own copy of this today, and the two have drifted.

- `definitions-wire.ts` mirrors the payload by hand, admitting both products'
  nullability variants, and reuses `GuardrailScope`/`GuardrailDefinitionStatus`
  so wire and display cannot drift.
- `definitions-parse.ts` validates with zod and never throws: a non-array sets
  `inputError`, one bad definition is dropped whole into `invalid`. Blank
  display strings are the single normalization, since one would beat curated
  copy and render an empty label; blank identifiers fail the entry instead of
  silently changing a definition's identity. zod stays private, pinned to the
  hand-written mirror by a bidirectional assignability check on the hot path
  plus a key-set test and a source-level import guard, so no schema type
  reaches the emitted `.d.ts`.
- `definitions-copy.ts` carries the six built-in validators' display copy as 63
  lingui messages in the shared canvas catalog, replacing Agents'
  `OOB_GUARDRAILS_I8N` and Flow's `buildValidatorDisplayInfo`. Ids use raw wire
  values, never a transcribed slug, which is how the two products ended up
  keying the same entity as `finNationalId` and `fiNationalId`. English only:
  the l10n sync owns the other catalogs, as it does for every other string here.
- `definitions-enrich.ts` resolves copy onto validated wire definitions. Pure
  and React-free, so Flow's vsix bridge calls it directly. Curated wins at
  definition level, wire wins at parameter level, BYO takes no curated copy.
- `use-guardrail-definitions.ts` composes the three. `options.definitions`
  skips the request entirely, so each product keeps its own transport. The
  context is compared by content rather than identity, unlike
  `useDiscoveryModels`, where an inline object refetches every render and never
  settles. Results carry the request key that produced them, so a tenant switch
  cannot keep serving the previous tenant's guardrails.

The 17 places the two products' English differs are each declared with a reason
in `definitions-parity.test.ts` and asserted against both products' transcribed
copy, so the suite fails on an undeclared difference or a wording we invented.

Also lands two things the leaf PRs were each carrying their own copy of, since
#1140, #1147 and #1161 all branch from here. `GuardrailStatusChip` is a
read-only pill for a guardrail row: deliberately not `GuardrailChip`, which
wraps a Radix `Toggle` and would put fake buttons in the tab order, and a
`<span>` composed from wind's `badgeVariants` rather than `Badge`, which renders
a `<div>` that is invalid inside the palette entry's `<button>`.
`GUARDRAIL_CHIP_GEOMETRY` is extracted so the interactive and read-only pills
stay one system. Its tones are harvested, not invented: `success` is the green
both products give a BYO origin or connector chip, `info` the blue both give a
"Preview" lifecycle label (Flow `bg-blue-100 text-blue-700`, Agents
`semantic.colorInfoBackground`), so neither product's colour changes when the
leaves adopt it. The label truncates with the full text on the chip's `title`,
because the pill is a fixed height and the text is the host's: a governance
label or a connector name long enough to wrap rendered two lines and spilled
out of its own background. The label sits in an inner span so it can truncate
at all, since `text-overflow` does not reach the anonymous flex item bare text
becomes inside `inline-flex`. `__fixtures__/catalog-coverage.ts` holds the catalog scans that
stand in for `lingui extract`, which never sees `src/canvas` because it uses no
macros.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andreizdrali-uipath added a commit that referenced this pull request Sep 16, 2026
…ilDefinitions [AL-574]

Turns the `GET /api/execution/guardrails/definitions` payload into the
`GuardrailDefinition`s `GuardrailBuilder` renders. Flow and Agents each carry
their own copy of this today, and the two have drifted.

- `definitions-wire.ts` mirrors the payload by hand, admitting both products'
  nullability variants, and reuses `GuardrailScope`/`GuardrailDefinitionStatus`
  so wire and display cannot drift.
- `definitions-parse.ts` validates with zod and never throws: a non-array sets
  `inputError`, one bad definition is dropped whole into `invalid`. Blank
  display strings are the single normalization, since one would beat curated
  copy and render an empty label; blank identifiers fail the entry instead of
  silently changing a definition's identity. zod stays private, pinned to the
  hand-written mirror by a bidirectional assignability check on the hot path
  plus a key-set test and a source-level import guard, so no schema type
  reaches the emitted `.d.ts`.
- `definitions-copy.ts` carries the six built-in validators' display copy as 63
  lingui messages in the shared canvas catalog, replacing Agents'
  `OOB_GUARDRAILS_I8N` and Flow's `buildValidatorDisplayInfo`. Ids use raw wire
  values, never a transcribed slug, which is how the two products ended up
  keying the same entity as `finNationalId` and `fiNationalId`. English only:
  the l10n sync owns the other catalogs, as it does for every other string here.
- `definitions-enrich.ts` resolves copy onto validated wire definitions. Pure
  and React-free, so Flow's vsix bridge calls it directly. Curated wins at
  definition level, wire wins at parameter level, BYO takes no curated copy.
- `use-guardrail-definitions.ts` composes the three. `options.definitions`
  skips the request entirely, so each product keeps its own transport. The
  context is compared by content rather than identity, unlike
  `useDiscoveryModels`, where an inline object refetches every render and never
  settles. Results carry the request key that produced them, so a tenant switch
  cannot keep serving the previous tenant's guardrails.

The 17 places the two products' English differs are each declared with a reason
in `definitions-parity.test.ts` and asserted against both products' transcribed
copy, so the suite fails on an undeclared difference or a wording we invented.

Also lands two things the leaf PRs were each carrying their own copy of, since
#1140, #1147 and #1161 all branch from here. `GuardrailStatusChip` is a
read-only pill for a guardrail row: deliberately not `GuardrailChip`, which
wraps a Radix `Toggle` and would put fake buttons in the tab order, and a
`<span>` composed from wind's `badgeVariants` rather than `Badge`, which renders
a `<div>` that is invalid inside the palette entry's `<button>`.
`GUARDRAIL_CHIP_GEOMETRY` is extracted so the interactive and read-only pills
stay one system. Its tones are harvested, not invented: `success` is the green
both products give a BYO origin or connector chip, `info` the blue both give a
"Preview" lifecycle label (Flow `bg-blue-100 text-blue-700`, Agents
`semantic.colorInfoBackground`), so neither product's colour changes when the
leaves adopt it. The label truncates with the full text on the chip's `title`,
because the pill is a fixed height and the text is the host's: a governance
label or a connector name long enough to wrap rendered two lines and spilled
out of its own background. The label sits in an inner span so it can truncate
at all, since `text-overflow` does not reach the anonymous flex item bare text
becomes inside `inline-flex`. `__fixtures__/catalog-coverage.ts` holds the catalog scans that
stand in for `lingui extract`, which never sees `src/canvas` because it uses no
macros.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ilDefinitions [AL-574]

Turns the `GET /api/execution/guardrails/definitions` payload into the
`GuardrailDefinition`s `GuardrailBuilder` renders. Flow and Agents each carry
their own copy of this today, and the two have drifted.

- `definitions-wire.ts` mirrors the payload by hand, admitting both products'
  nullability variants, and reuses `GuardrailScope`/`GuardrailDefinitionStatus`
  so wire and display cannot drift.
- `definitions-parse.ts` validates with zod and never throws: a non-array sets
  `inputError`, one bad definition is dropped whole into `invalid`. Blank
  display strings are the single normalization, since one would beat curated
  copy and render an empty label; blank identifiers fail the entry instead of
  silently changing a definition's identity. zod stays private, pinned to the
  hand-written mirror by a bidirectional assignability check on the hot path
  plus a key-set test and a source-level import guard, so no schema type
  reaches the emitted `.d.ts`.
- `definitions-copy.ts` carries the six built-in validators' display copy as 63
  lingui messages in the shared canvas catalog, replacing Agents'
  `OOB_GUARDRAILS_I8N` and Flow's `buildValidatorDisplayInfo`. Ids use raw wire
  values, never a transcribed slug, which is how the two products ended up
  keying the same entity as `finNationalId` and `fiNationalId`. English only:
  the l10n sync owns the other catalogs, as it does for every other string here.
- `definitions-enrich.ts` resolves copy onto validated wire definitions. Pure
  and React-free, so Flow's vsix bridge calls it directly. Curated wins at
  definition level, wire wins at parameter level, BYO takes no curated copy.
- `use-guardrail-definitions.ts` composes the three. `options.definitions`
  skips the request entirely, so each product keeps its own transport. The
  context is compared by content rather than identity, unlike
  `useDiscoveryModels`, where an inline object refetches every render and never
  settles. Results carry the request key that produced them, so a tenant switch
  cannot keep serving the previous tenant's guardrails.

The 17 places the two products' English differs are each declared with a reason
in `definitions-parity.test.ts` and asserted against both products' transcribed
copy, so the suite fails on an undeclared difference or a wording we invented.

Also lands two things the leaf PRs were each carrying their own copy of, since
#1140, #1147 and #1161 all branch from here. `GuardrailStatusChip` is a
read-only pill for a guardrail row: deliberately not `GuardrailChip`, which
wraps a Radix `Toggle` and would put fake buttons in the tab order, and a
`<span>` composed from wind's `badgeVariants` rather than `Badge`, which renders
a `<div>` that is invalid inside the palette entry's `<button>`.
`GUARDRAIL_CHIP_GEOMETRY` is extracted so the interactive and read-only pills
stay one system. Its tones are harvested, not invented: `success` is the green
both products give a BYO origin or connector chip, `info` the blue both give a
"Preview" lifecycle label (Flow `bg-blue-100 text-blue-700`, Agents
`semantic.colorInfoBackground`), so neither product's colour changes when the
leaves adopt it. The label truncates with the full text on the chip's `title`,
because the pill is a fixed height and the text is the host's: a governance
label or a connector name long enough to wrap rendered two lines and spilled
out of its own background. The label sits in an inner span so it can truncate
at all, since `text-overflow` does not reach the anonymous flex item bare text
becomes inside `inline-flex`. `__fixtures__/catalog-coverage.ts` holds the catalog scans that
stand in for `lingui extract`, which never sees `src/canvas` because it uses no
macros.

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Several valid lifecycle and policy inputs can currently show stale, misleading, duplicated, or overflowing guardrail content.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:40

  • This delimiter-based key is not injective even though executionStage is intentionally an open string and validator values are wire data. For example, validator: 'a-b', executionStage: 'Pre' collides with validator: 'a', executionStage: 'b-Pre', producing one React key and incorrect row reconciliation. Encode the tuple rather than concatenating unescaped fields.
  return `${guardrail.validator}-${guardrail.executionStage}-${origin}`;
  • Files reviewed: 38/38 changed files
  • Comments generated: 4
  • Review effort level: Lite

copy,
}: { definition?: CentralizedGuardrailDefinition; copy?: GuardrailCopyTable } = {}
): { name: string; description?: string } {
const curated = copy?.[guardrail.validator];
Comment on lines +234 to +256
for (const parameterDefinition of definitions) {
if (
parameterDefinition.keySource !== undefined &&
valuesById.has(parameterDefinition.id) &&
rendersAsThresholds(parameterDefinition.id)
) {
consumedIds.add(parameterDefinition.keySource);
}
}

const toThresholdRow = (
id: string,
parameterDefinition: CentralizedGuardrailParameterDefinition | undefined
): CentralizedGuardrailParameterRow | undefined => {
const thresholds = asNumberRecord(valuesById.get(id));
const keySourceDefinition =
parameterDefinition?.keySource !== undefined
? definitionsById.get(parameterDefinition.keySource)
: undefined;
const keySourceList =
keySourceDefinition?.type === 'enum-list' ? keySourceDefinition : undefined;
const selectedKeys = keySourceList ? asStringArray(valuesById.get(keySourceList.id)) : [];
const keys = Array.from(new Set([...Object.keys(thresholds), ...selectedKeys]));
)}
<span className="min-w-0 flex-1">
<span className="flex min-w-0 items-center gap-2">
<span className="truncate text-sm font-medium">{name}</span>
Comment on lines +203 to +206
// Only the in-flight flag needs clearing; the results are already unreachable, since
// their stamp cannot match a disabled hook's request.
abortRef.current?.abort();
setInFlight(false);
Copilot AI review requested due to automatic review settings September 17, 2026 14:09
@andreizdrali-uipath
andreizdrali-uipath force-pushed the feat/apollo-react-guardrail-centralized-section branch from 7c37e6f to 414a8e3 Compare September 17, 2026 14:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Two resolver paths can silently drop malformed BYO values or mislabel nameless BYO records.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:104

  • For a BYO policy record with a missing or null name, this falls through to the curated table even though the record is explicitly marked BYO. A BYO pii_detection can therefore be displayed as the built-in “PII detection”, contradicting the origin and the helper's zero-curated-copy contract. Only use curated display names for non-BYO records, then fall back to the raw validator.
    name: guardrail.name ?? curated?.displayName ?? guardrail.validator,
  • Files reviewed: 38/38 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +200 to +210
const typesById = new Map(
parameters.map((parameter) => [parameter.id, parameter.parameterType ?? undefined])
);
const definitionsById = new Map(
definitions.map((parameterDefinition) => [parameterDefinition.id, parameterDefinition])
);

const rendersAsThresholds = (id: string): boolean =>
definitionsById.get(id)?.type === 'map-enum' ||
typesById.get(id) === 'map-enum' ||
isPlainObject(valuesById.get(id));
…ils [AL-578]

The read-only list of guardrails an organization's AI Trust Layer governance
policy enforces on an agent, and the details content behind a row. Both
products ship this today, each with its own copy; this is one component for
both.

- `CentralizedGuardrailsSection` renders the rows: name, origin chip, and for a
  broken BYO configuration a status chip plus the sentence that says what to do
  about it. `onSelect` hands the selection back, because opening the details is
  host orchestration: Agents uses a dialog, Flow pushes a panel overlay.
- `CentralizedGuardrailDetails` is that content, not a shell. The policy fields
  and the configuration render as a description list rather than the family's
  parameter editors in a read-only state: those are the MetadataForm stack,
  which has no read-only mode, and a disabled input cannot be focused, so its
  value is not selectable, not copyable and skipped by a screen reader.
- `centralized-types.ts` mirrors both products' policy schemas structurally, so
  a host passes its own zod-inferred types with no mapping. A governance record
  is not a variant of `GuardrailBuilderValue`: no `id`, `scopes` at the top
  level, `action` a bare discriminator.
- `resolveCentralizedGuardrailParameters` lifts a built-in's `entities` and
  `entityThresholds` onto the parameter shape, so one resolver covers both
  origins, and labels them from the matching definition when there is one.
- `definitions` is optional and `undefined` means "not loaded yet", which is
  what keeps a row from claiming a configuration was deleted while the catalog
  is still in flight.
- The row's accessible name is its own text. Both products put an `aria-label`
  there, which overrides the content and hides the description, the provider
  and the remediation message from screen readers.
- `guardrails.centralized.*` ids in one builder function, English harvested
  from both products where they agree. The five divergences are declared with a
  reason in `centralized-parity.test.ts`; scope, action, type and description
  labels reuse the builder's ids rather than twinning them.
- `GuardrailStatusBanner` and the details' note pass `mt-0`: wind's
  `AlertDescription` is unconditionally `mt-1`, which assumes an `AlertTitle`
  above it, so a title-less alert renders its text below its icon.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 17, 2026 14:43
@andreizdrali-uipath
andreizdrali-uipath force-pushed the feat/apollo-react-guardrail-centralized-section branch from 414a8e3 to 03e5e3a Compare September 17, 2026 14:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The definitions hook can reuse stale results after disable/re-enable, and unnamed BYO records can be mislabeled with built-in copy.

Review details

Suppressed comments (2)

packages/apollo-react/src/canvas/components/Guardrails/use-guardrail-definitions.ts:206

  • Disabling only makes the previous settled value unreachable while requestKey is empty; it never clears that value. If the same context is enabled again, its key matches the old result immediately, so the hook renders the previous catalog (and old error) while the new request is in flight, contradicting the documented “disabling clears the fetched state” behavior. Reset the settled state on this path and cover a disable-then-enable cycle with the same context.
    packages/apollo-react/src/canvas/components/Guardrails/centralized-guardrail-utils.ts:105
  • For a BYO record whose nullable/optional name is missing, this falls through to the curated built-in display name when the validator id collides (for example, pii_detection). The row then identifies the entry as “PII detection” while marking it BYO or missing, even though the description and definition matching correctly treat it as BYO. Only use curated display copy for non-BYO records; otherwise fall back to the raw validator (or another explicit BYO fallback).
  const curated = copy?.[guardrail.validator];
  const description = guardrail.isByo ? definition?.description : curated?.description;
  return {
    name: guardrail.name ?? curated?.displayName ?? guardrail.validator,
    description: description === undefined || description === '' ? undefined : description,
  • Files reviewed: 38/38 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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

Labels

dev-packages Adds dev package publishing on pushes to this PR pkg:apollo-react size:XXL 1,000+ changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants