Skip to content

feat(davinci-client): add ValidatedPasswordCollector with embedded password policy#572

Open
ryanbas21 wants to merge 1 commit intomainfrom
password-rules-toplevel
Open

feat(davinci-client): add ValidatedPasswordCollector with embedded password policy#572
ryanbas21 wants to merge 1 commit intomainfrom
password-rules-toplevel

Conversation

@ryanbas21
Copy link
Copy Markdown
Collaborator

@ryanbas21 ryanbas21 commented Apr 16, 2026

Summary

  • Splits PASSWORD and PASSWORD_VERIFY fields into two distinct collector types: PasswordCollector (no policy) and ValidatedPasswordCollector (carries an embedded passwordPolicy).
  • Wires client.validate() to run a value through the embedded policy when given a ValidatedPasswordCollector.
  • Updates the sample app to render policy requirements from the collector.

What changed

Area Change
Types New PasswordPolicy interface (davinci.types.ts); new ValidatedPasswordCollector interface (collector.types.ts); PasswordField extracted from StandardField to carry optional passwordPolicy.
Collector factories New returnValidatedPasswordCollector(); existing returnPasswordCollector() unchanged.
Reducer Routes by field.type: PASSWORDPasswordCollector; PASSWORD_VERIFYValidatedPasswordCollector (the embedded passwordPolicy is attached when present, otherwise an empty policy object is emitted).
Validation New password-policy.rules.ts module: pure rule functions (length, minUniqueCharacters, maxRepeatedCharacters, minCharacters) and returnPasswordPolicyValidator(collector).
client.validate() Routes ValidatedPasswordCollector through returnPasswordPolicyValidator; returns a descriptive error when called on a plain PasswordCollector.
Type plumbing SingleValueCollectorTypes, InferSingleValueCollectorType, SingleValueCollectors, Collectors, CollectorValueType updated to include ValidatedPasswordCollector.
Sample app Password component renders the policy requirements list when the collector carries one.
Mock data PASSWORD_VERIFY field carries the policy (Solution 2 shape — embedded in the field, no root-level duplicate).

Test plan

  • nx test davinci-client — 312 tests pass (22 files)
  • nx typecheck davinci-client — clean
  • nx lint davinci-client — no new warnings
  • API reports regenerated
  • Manual verification of the sample app against a live DaVinci flow with PASSWORD_VERIFY + feature flag dv-16053-embed_password_policy_in_component
Screenshot 2026-04-22 at 1 48 13 PM Screenshot 2026-04-22 at 1 48 21 PM Screenshot 2026-04-22 at 1 48 28 PM

Summary by CodeRabbit

  • New Features
    • Password verification fields now display password policy requirements as a checklist.
    • Real-time validation feedback for password input, with errors displayed on change.
    • Password policies embedded from the server are enforced during client-side validation, including length constraints, minimum unique characters, character repetition limits, and character set requirements.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 16, 2026

🦋 Changeset detected

Latest commit: d8648fb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@forgerock/davinci-client Minor
@forgerock/device-client Minor
@forgerock/journey-client Minor
@forgerock/oidc-client Minor
@forgerock/protect Minor
@forgerock/sdk-types Minor
@forgerock/sdk-utilities Minor
@forgerock/iframe-manager Minor
@forgerock/sdk-logger Minor
@forgerock/sdk-oidc Minor
@forgerock/sdk-request-middleware Minor
@forgerock/storage Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR introduces support for a new ValidatedPasswordCollector type that embeds server-provided password policy constraints. The reducer now routes PASSWORD_VERIFY fields to this collector while PASSWORD fields use the standard PasswordCollector. A new password policy validation module enforces constraints on length, unique characters, repeated characters, and per-charset minimums. Both collector types now track verification state via output.verify.

Changes

Cohort / File(s) Summary
Type Definitions & Field Models
packages/davinci-client/src/lib/davinci.types.ts, packages/davinci-client/src/lib/collector.types.ts, packages/davinci-client/src/lib/node.types.ts, packages/davinci-client/src/lib/client.types.ts
Added PasswordField and PasswordPolicy types; removed password field types from StandardField; added ValidatedPasswordCollector to collector unions; reordered conditional type mappings in CollectorValueType for correctness.
Collector Factories
packages/davinci-client/src/lib/collector.utils.ts
Updated returnSingleValueCollector to handle PasswordField inputs; added logic to derive verify and passwordPolicy from fields; introduced new returnValidatedPasswordCollector factory function.
Password Policy Validation
packages/davinci-client/src/lib/password-policy.rules.ts
New module implementing password policy constraint rules for length, unique characters, repeated characters, and per-charset minimums; exports returnPasswordPolicyValidator function.
Reducer Logic
packages/davinci-client/src/lib/node.reducer.ts
Added distinct routing: PASSWORD field type calls returnPasswordCollector, while PASSWORD_VERIFY calls returnValidatedPasswordCollector; updated state union types.
Client Store
packages/davinci-client/src/lib/client.store.ts
Updated validate(...) to branch by collector type: PasswordCollector returns validation error directing users to use ValidatedPasswordCollector; ValidatedPasswordCollector uses password policy validator directly.
E2E Demo App
e2e/davinci-app/components/password.ts, e2e/davinci-app/main.ts
Password component now accepts `PasswordCollector
Mock Data
packages/davinci-client/src/lib/mock-data/mock-form-fields.data.ts, packages/davinci-client/src/lib/mock-data/node.next.mock.ts
Added PASSWORD_VERIFY field with inline password policy; updated form data to include password field; updated mock collector output to include verify: false.
Type Testing
packages/davinci-client/src/lib/collector.types.test-d.ts, packages/davinci-client/src/lib/node.types.test-d.ts, packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts
Extended type tests to verify ValidatedPasswordCollector properties and correct type narrowing; updated PasswordCollector output assertions to include verify.
Unit Tests
packages/davinci-client/src/lib/collector.utils.test.ts, packages/davinci-client/src/lib/node.reducer.test.ts, packages/davinci-client/src/lib/davinci.utils.test.ts
Added comprehensive test coverage for ValidatedPasswordCollector creation, policy embedding, and policy validator behavior; updated password collector tests to require verify: false in output; added PASSWORD_VERIFY field routing test.
Public API Reports
packages/davinci-client/api-report/davinci-client.api.md, packages/davinci-client/api-report/davinci-client.types.api.md
Updated exported type surface to include ValidatedPasswordCollector, PasswordField, and PasswordPolicy; replaced PasswordCollector type alias with explicit interface.
Documentation
.changeset/embed-password-policy-in-component.md
Added changelog entry documenting new ValidatedPasswordCollector, policy routing behavior, and validation semantics.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • cerebrl
  • ancheetah

Poem

🐰 A password collector, refined and made whole,
Now validates policies with care and with soul,
PASSWORD and PASSWORD_VERIFY align,
With rules that ensure every character's divine,
Hop along, dear reviewers, the logic's all fine! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding ValidatedPasswordCollector with embedded password policy.
Description check ✅ Passed The PR description includes a detailed summary, what changed (with a comprehensive table), test results, but lacks a Jira ticket reference as specified in the template.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch password-rules-toplevel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented Apr 16, 2026

View your CI Pipeline Execution ↗ for commit d8648fb

Command Status Duration Result
nx affected -t build lint test typecheck e2e-ci ❌ Failed 2m 37s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-30 20:39:19 UTC

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
packages/davinci-client/src/lib/collector.types.ts (1)

193-210: Consider extending SingleValueCollectorNoValue instead of hand-rolling the shape.

PasswordVerifyCollector is structurally identical to SingleValueCollectorNoValue<'PasswordVerifyCollector'> plus optional output.passwordPolicy. Defining it as an intersection would reduce drift risk if the base shape evolves (e.g., input.validation, etc.).

Alternative definition
-export interface PasswordVerifyCollector {
-  category: 'SingleValueCollector';
-  error: string | null;
-  type: 'PasswordVerifyCollector';
-  id: string;
-  name: string;
-  input: {
-    key: string;
-    value: string | number | boolean;
-    type: string;
-  };
-  output: {
-    key: string;
-    label: string;
-    type: string;
-    passwordPolicy?: PasswordPolicy;
-  };
-}
+export type PasswordVerifyCollector = SingleValueCollectorNoValue<'PasswordVerifyCollector'> & {
+  output: { passwordPolicy?: PasswordPolicy };
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/collector.types.ts` around lines 193 - 210,
PasswordVerifyCollector duplicates the shape of
SingleValueCollectorNoValue<'PasswordVerifyCollector'> and risks drifting;
refactor PasswordVerifyCollector to extend or be defined as an intersection with
SingleValueCollectorNoValue<'PasswordVerifyCollector'> and add the optional
output.passwordPolicy field, so the type becomes
SingleValueCollectorNoValue<'PasswordVerifyCollector'> & { output: {
passwordPolicy?: PasswordPolicy } } (or equivalent), keeping the unique type tag
'PasswordVerifyCollector' and preserving existing fields like input/output.
packages/davinci-client/src/lib/davinci.types.ts (1)

64-83: Consider modeling environment on PasswordPolicy.

The mock in mock-form-fields.data.ts (lines 51-54) includes environment: { id: string } on the embedded policy, matching what PingOne returns. Since PasswordPolicy is the contract consumers will type against when reading collector.output.passwordPolicy, missing this field means users accessing policy.environment?.id would need a cast. Consider adding:

Proposed addition
 export interface PasswordPolicy {
   id?: string;
+  environment?: { id?: string };
   name?: string;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/davinci.types.ts` around lines 64 - 83, The
PasswordPolicy interface is missing an environment property so consumers reading
collector.output.passwordPolicy must cast to access environment.id; update the
PasswordPolicy type to include environment?: { id?: string } (or a more complete
environment shape if available) so the mock shape in mock-form-fields.data.ts
matches the typed contract; ensure references to PasswordPolicy (e.g., usages of
collector.output.passwordPolicy) compile without casts after this change.
packages/davinci-client/src/lib/collector.utils.ts (1)

450-483: Duplicates error-building logic from returnSingleValueCollector.

The key/label/type presence checks here mirror the block in returnSingleValueCollector (lines 154-163). Since PasswordVerifyField is a compile-time-narrowed type with required key, label, type, these in checks never fail at runtime for well-typed callers — they exist only as defensive parity with the shared helper. Consider delegating to returnSingleValueCollector with a 'PasswordVerifyCollector' branch, then adding passwordPolicy on top, to keep validation centralized.

Non-blocking; current implementation is correct.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/collector.utils.ts` around lines 450 - 483,
Replace the duplicated presence-check logic in returnPasswordVerifyCollector by
calling the shared helper returnSingleValueCollector (passing the same field and
idx and specifying type 'PasswordVerifyCollector' or otherwise using its branch)
to produce the base collector, then extend/override that returned object's type
and output to include the passwordPolicy when present; keep references to
PasswordVerifyCollector and PasswordVerifyField so the resulting collector adds
the passwordPolicy field on top of the single-value collector instead of
reimplementing the key/label/type checks.
e2e/davinci-app/components/password.ts (2)

46-54: Prefer RegExp.test() over String.prototype.match() for boolean checks.

match() allocates a result array; test() returns a boolean directly and reads more intentionally here. Also, consider hoisting the regexes so they aren't recompiled on every iteration.

♻️ Suggested refactor
+    const UPPER_RE = /^[A-Z]+$/;
+    const LOWER_RE = /^[a-z]+$/;
+    const DIGIT_RE = /^[0-9]+$/;
     if (policy.minCharacters) {
       for (const [charset, count] of Object.entries(policy.minCharacters)) {
         const li = document.createElement('li');
-        if (charset.match(/^[A-Z]+$/)) {
+        if (UPPER_RE.test(charset)) {
           li.textContent = `At least ${count} uppercase letter(s)`;
-        } else if (charset.match(/^[a-z]+$/)) {
+        } else if (LOWER_RE.test(charset)) {
           li.textContent = `At least ${count} lowercase letter(s)`;
-        } else if (charset.match(/^[0-9]+$/)) {
+        } else if (DIGIT_RE.test(charset)) {
           li.textContent = `At least ${count} number(s)`;
         } else {
           li.textContent = `At least ${count} special character(s)`;
         }
         requirementsList.appendChild(li);
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/davinci-app/components/password.ts` around lines 46 - 54, The code in
password.ts uses String.prototype.match() for boolean checks inside the block
that sets li.textContent; replace those match() calls with precompiled
RegExp.test() calls to avoid allocating arrays and improve intent: create
hoisted regex constants (e.g., UPPERCASE_REGEX, LOWERCASE_REGEX, DIGIT_REGEX,
SPECIAL_REGEX) outside the loop or function and then use
UPPERCASE_REGEX.test(charset), LOWERCASE_REGEX.test(charset),
DIGIT_REGEX.test(charset), else SPECIAL_REGEX.test(charset) to pick the
appropriate message for the li element (the code paths around li.textContent
remain the same).

31-62: Optional: associate the requirements list with the password input for a11y.

Even for an e2e sample, wiring aria-describedby on the <input> to an id on the <ul> (or using aria-labelledby with a heading) makes the intent visible to assistive tech and is a nicer reference pattern for SDK consumers to copy. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/davinci-app/components/password.ts` around lines 31 - 62, Add an
accessible reference between the rendered requirements list and the password
input: give the created requirementsList element a stable id (e.g.,
"password-requirements-<unique>") and set that id on the password input's
aria-describedby so assistive tech can read the requirements; locate the
password input where the form is built (the password input variable or the
element found via selector) and assign aria-describedby to match
requirementsList.id only when requirementsList is appended (use the same
conditional that appends requirementsList to formEl and clean up/reset the
attribute if policy is not present).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/davinci-app/components/password.ts`:
- Around line 37-41: The current rendering uses policy.length.min and
policy.length.max directly, which produces "undefined" when one side is missing;
update the logic around policy.length to check length.min and length.max
independently (e.g., if both present render "min–max characters", if only min
render "at least {min} characters", if only max render "up to {max}
characters"), only create/append the li element to requirementsList when at
least one of length.min or length.max exists, and use the existing li variable
for the text content assignment.

---

Nitpick comments:
In `@e2e/davinci-app/components/password.ts`:
- Around line 46-54: The code in password.ts uses String.prototype.match() for
boolean checks inside the block that sets li.textContent; replace those match()
calls with precompiled RegExp.test() calls to avoid allocating arrays and
improve intent: create hoisted regex constants (e.g., UPPERCASE_REGEX,
LOWERCASE_REGEX, DIGIT_REGEX, SPECIAL_REGEX) outside the loop or function and
then use UPPERCASE_REGEX.test(charset), LOWERCASE_REGEX.test(charset),
DIGIT_REGEX.test(charset), else SPECIAL_REGEX.test(charset) to pick the
appropriate message for the li element (the code paths around li.textContent
remain the same).
- Around line 31-62: Add an accessible reference between the rendered
requirements list and the password input: give the created requirementsList
element a stable id (e.g., "password-requirements-<unique>") and set that id on
the password input's aria-describedby so assistive tech can read the
requirements; locate the password input where the form is built (the password
input variable or the element found via selector) and assign aria-describedby to
match requirementsList.id only when requirementsList is appended (use the same
conditional that appends requirementsList to formEl and clean up/reset the
attribute if policy is not present).

In `@packages/davinci-client/src/lib/collector.types.ts`:
- Around line 193-210: PasswordVerifyCollector duplicates the shape of
SingleValueCollectorNoValue<'PasswordVerifyCollector'> and risks drifting;
refactor PasswordVerifyCollector to extend or be defined as an intersection with
SingleValueCollectorNoValue<'PasswordVerifyCollector'> and add the optional
output.passwordPolicy field, so the type becomes
SingleValueCollectorNoValue<'PasswordVerifyCollector'> & { output: {
passwordPolicy?: PasswordPolicy } } (or equivalent), keeping the unique type tag
'PasswordVerifyCollector' and preserving existing fields like input/output.

In `@packages/davinci-client/src/lib/collector.utils.ts`:
- Around line 450-483: Replace the duplicated presence-check logic in
returnPasswordVerifyCollector by calling the shared helper
returnSingleValueCollector (passing the same field and idx and specifying type
'PasswordVerifyCollector' or otherwise using its branch) to produce the base
collector, then extend/override that returned object's type and output to
include the passwordPolicy when present; keep references to
PasswordVerifyCollector and PasswordVerifyField so the resulting collector adds
the passwordPolicy field on top of the single-value collector instead of
reimplementing the key/label/type checks.

In `@packages/davinci-client/src/lib/davinci.types.ts`:
- Around line 64-83: The PasswordPolicy interface is missing an environment
property so consumers reading collector.output.passwordPolicy must cast to
access environment.id; update the PasswordPolicy type to include environment?: {
id?: string } (or a more complete environment shape if available) so the mock
shape in mock-form-fields.data.ts matches the typed contract; ensure references
to PasswordPolicy (e.g., usages of collector.output.passwordPolicy) compile
without casts after this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 369808f1-fb4b-4b90-aba0-5b1ec2fe0fe2

📥 Commits

Reviewing files that changed from the base of the PR and between 9088443 and 244dbf4.

📒 Files selected for processing (17)
  • .changeset/embed-password-policy-in-component.md
  • e2e/davinci-app/components/password.ts
  • e2e/davinci-app/main.ts
  • packages/davinci-client/api-report/davinci-client.api.md
  • packages/davinci-client/api-report/davinci-client.types.api.md
  • packages/davinci-client/src/lib/client.types.ts
  • packages/davinci-client/src/lib/collector.types.test-d.ts
  • packages/davinci-client/src/lib/collector.types.ts
  • packages/davinci-client/src/lib/collector.utils.test.ts
  • packages/davinci-client/src/lib/collector.utils.ts
  • packages/davinci-client/src/lib/davinci.types.ts
  • packages/davinci-client/src/lib/mock-data/mock-form-fields.data.ts
  • packages/davinci-client/src/lib/node.reducer.test.ts
  • packages/davinci-client/src/lib/node.reducer.ts
  • packages/davinci-client/src/lib/node.types.test-d.ts
  • packages/davinci-client/src/lib/node.types.ts
  • packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts

Comment thread e2e/davinci-app/components/password.ts Outdated
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 16, 2026

Open in StackBlitz

@forgerock/davinci-client

pnpm add https://pkg.pr.new/@forgerock/davinci-client@572

@forgerock/device-client

pnpm add https://pkg.pr.new/@forgerock/device-client@572

@forgerock/journey-client

pnpm add https://pkg.pr.new/@forgerock/journey-client@572

@forgerock/oidc-client

pnpm add https://pkg.pr.new/@forgerock/oidc-client@572

@forgerock/protect

pnpm add https://pkg.pr.new/@forgerock/protect@572

@forgerock/sdk-types

pnpm add https://pkg.pr.new/@forgerock/sdk-types@572

@forgerock/sdk-utilities

pnpm add https://pkg.pr.new/@forgerock/sdk-utilities@572

@forgerock/iframe-manager

pnpm add https://pkg.pr.new/@forgerock/iframe-manager@572

@forgerock/sdk-logger

pnpm add https://pkg.pr.new/@forgerock/sdk-logger@572

@forgerock/sdk-oidc

pnpm add https://pkg.pr.new/@forgerock/sdk-oidc@572

@forgerock/sdk-request-middleware

pnpm add https://pkg.pr.new/@forgerock/sdk-request-middleware@572

@forgerock/storage

pnpm add https://pkg.pr.new/@forgerock/storage@572

commit: 11d4052

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 16, 2026

Codecov Report

❌ Patch coverage is 86.40000% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 17.85%. Comparing base (5d6747a) to head (11d4052).
⚠️ Report is 83 commits behind head on main.

Files with missing lines Patch % Lines
packages/davinci-client/src/lib/client.store.ts 0.00% 16 Missing ⚠️
...es/davinci-client/src/lib/password-policy.rules.ts 98.52% 1 Missing ⚠️

❌ Your project status has failed because the head coverage (17.85%) is below the target coverage (40.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##             main     #572       +/-   ##
===========================================
- Coverage   70.90%   17.85%   -53.06%     
===========================================
  Files          53      155      +102     
  Lines        2021    24271    +22250     
  Branches      377     1181      +804     
===========================================
+ Hits         1433     4333     +2900     
- Misses        588    19938    +19350     
Files with missing lines Coverage Δ
packages/davinci-client/src/lib/client.types.ts 100.00% <ø> (ø)
packages/davinci-client/src/lib/collector.types.ts 100.00% <ø> (ø)
packages/davinci-client/src/lib/collector.utils.ts 83.60% <100.00%> (ø)
packages/davinci-client/src/lib/davinci.types.ts 100.00% <ø> (ø)
packages/davinci-client/src/lib/node.reducer.ts 71.77% <100.00%> (ø)
packages/davinci-client/src/lib/node.types.ts 100.00% <ø> (ø)
...es/davinci-client/src/lib/password-policy.rules.ts 98.52% <98.52%> (ø)
packages/davinci-client/src/lib/client.store.ts 0.28% <0.00%> (ø)

... and 94 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

Deployed f8ba74f to https://ForgeRock.github.io/ping-javascript-sdk/pr-572/f8ba74f0cbd26640e625ab8daabd455d33988a9c branch gh-pages in ForgeRock/ping-javascript-sdk

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

📦 Bundle Size Analysis

📦 Bundle Size Analysis

🚨 Significant Changes

🔻 @forgerock/device-client - 0.0 KB (-10.0 KB, -100.0%)
🔺 @forgerock/davinci-client - 51.2 KB (+2.9 KB, +6.0%)
🔻 @forgerock/journey-client - 0.0 KB (-90.3 KB, -100.0%)

➖ No Changes

@forgerock/device-client - 10.0 KB
@forgerock/oidc-client - 25.2 KB
@forgerock/sdk-utilities - 11.2 KB
@forgerock/sdk-types - 7.9 KB
@forgerock/protect - 144.6 KB
@forgerock/journey-client - 90.3 KB
@forgerock/storage - 1.5 KB
@forgerock/sdk-oidc - 4.8 KB
@forgerock/sdk-request-middleware - 4.5 KB
@forgerock/sdk-logger - 1.6 KB
@forgerock/iframe-manager - 2.4 KB


14 packages analyzed • Baseline from latest main build

Legend

🆕 New package
🔺 Size increased
🔻 Size decreased
➖ No change

ℹ️ How bundle sizes are calculated
  • Current Size: Total gzipped size of all files in the package's dist directory
  • Baseline: Comparison against the latest build from the main branch
  • Files included: All build outputs except source maps and TypeScript build cache
  • Exclusions: .map, .tsbuildinfo, and .d.ts.map files

🔄 Updated automatically on each push to this PR

Copy link
Copy Markdown
Collaborator

@cerebrl cerebrl left a comment

Choose a reason for hiding this comment

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

Can we check to see if the native platforms are providing a .validate() function for this collector? We have this for ValidatedSingleValueCollector.

Comment thread e2e/davinci-app/components/password.ts Outdated
const requirementsList = document.createElement('ul');
requirementsList.className = 'password-requirements';

if (policy.length) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Wow, this really threw me. I was thinking policy was an array due to the .length use, which made the other code confusing :)

}

// Component-level policy takes precedence over root-level
const passwordPolicy = field.passwordPolicy ?? rootPasswordPolicy;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we want to fallback to this "root" policy? Is this what the other platforms are doing?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No they aren't it was just a best guess i can remove it.


export type SingleValueCollectors =
| SingleValueCollectorNoValue<'PasswordCollector'>
| PasswordVerifyCollector
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm curious as to why you're treating this new collector differently than the others. Could you elaborate on the reason for deviation?

* @param {PasswordPolicy} [rootPasswordPolicy] - Optional root-level password policy for backward compatibility.
* @returns {PasswordVerifyCollector} The constructed PasswordVerifyCollector object.
*/
export function returnPasswordVerifyCollector(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we extend/reuse the PasswordCollector functions and just add the passwordPolicy property to the collector output?

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/davinci-client/src/lib/collector.types.test-d.ts (1)

147-156: ⚠️ Potential issue | 🟡 Minor

Make the SingleValueCollectorTypes fixture actually exhaustive.

Because validTypes is annotated as SingleValueCollectorTypes[], (typeof validTypes)[number] widens to the full union even when the array omits 'ValidatedPasswordCollector'.

🧪 Proposed type-test fix
-      const validTypes: SingleValueCollectorTypes[] = [
+      const validTypes = [
         'TextCollector',
         'PasswordCollector',
+        'ValidatedPasswordCollector',
+        'SingleValueCollector',
         'SingleSelectCollector',
-      ];
+        'SingleSelectObjectCollector',
+        'ValidatedTextCollector',
+      ] as const satisfies readonly SingleValueCollectorTypes[];

       // Type assertion to ensure SingleValueCollectorTypes includes all these values
       expectTypeOf<SingleValueCollectorTypes>().toEqualTypeOf<(typeof validTypes)[number]>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/collector.types.test-d.ts` around lines 147 -
156, The test's fixture isn't truly exhaustive because annotating validTypes as
SingleValueCollectorTypes[] widens the type; change validTypes to a const
literal tuple (remove the explicit SingleValueCollectorTypes[] annotation and
use a const assertion) and include the missing 'ValidatedPasswordCollector'
value so the const tuple equals the exact union; keep the assertion
expectTypeOf<SingleValueCollectorTypes>().toEqualTypeOf<(typeof
validTypes)[number]>(), referencing SingleValueCollectorTypes and the validTypes
variable to force compile-time exhaustiveness.
🧹 Nitpick comments (1)
packages/davinci-client/src/lib/davinci.types.ts (1)

91-98: Consider discriminated unions to strengthen type safety for PASSWORD_VERIFY.

The code intentionally separates the PASSWORD_VERIFY type tag from the verify flag because collector selection depends on passwordPolicy presence, not the type. However, documenting that the server sends verify: true alongside PASSWORD_VERIFY doesn't enforce that relationship at the type level.

A discriminated union would make this semantic explicit and prevent future misuse:

Optional type refinement
-export type PasswordField = {
-  type: 'PASSWORD' | 'PASSWORD_VERIFY';
+type PasswordFieldBase = {
   key: string;
   label: string;
   required?: boolean;
-  verify?: boolean;
   passwordPolicy?: PasswordPolicy;
 };
+
+export type PasswordField =
+  | (PasswordFieldBase & { type: 'PASSWORD'; verify?: false })
+  | (PasswordFieldBase & { type: 'PASSWORD_VERIFY'; verify?: true });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/davinci.types.ts` around lines 91 - 98, The
PasswordField type currently allows any combination of type ('PASSWORD' |
'PASSWORD_VERIFY') with optional verify and passwordPolicy, which doesn't
enforce the invariant that when type === 'PASSWORD_VERIFY' the server includes
verify: true; refactor PasswordField into a discriminated union with two
variants (e.g., PasswordFieldPassword and PasswordFieldPasswordVerify) so that
the 'type' field discriminates: the 'PASSWORD_VERIFY' variant must include
verify: true (and any fields required for that case) while the 'PASSWORD'
variant has verify?: boolean and optional passwordPolicy as before; update
usages that construct or consume PasswordField to handle the two variants (using
type narrowing) to preserve type safety around verify and passwordPolicy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/davinci-client/src/lib/collector.types.test-d.ts`:
- Around line 147-156: The test's fixture isn't truly exhaustive because
annotating validTypes as SingleValueCollectorTypes[] widens the type; change
validTypes to a const literal tuple (remove the explicit
SingleValueCollectorTypes[] annotation and use a const assertion) and include
the missing 'ValidatedPasswordCollector' value so the const tuple equals the
exact union; keep the assertion
expectTypeOf<SingleValueCollectorTypes>().toEqualTypeOf<(typeof
validTypes)[number]>(), referencing SingleValueCollectorTypes and the validTypes
variable to force compile-time exhaustiveness.

---

Nitpick comments:
In `@packages/davinci-client/src/lib/davinci.types.ts`:
- Around line 91-98: The PasswordField type currently allows any combination of
type ('PASSWORD' | 'PASSWORD_VERIFY') with optional verify and passwordPolicy,
which doesn't enforce the invariant that when type === 'PASSWORD_VERIFY' the
server includes verify: true; refactor PasswordField into a discriminated union
with two variants (e.g., PasswordFieldPassword and PasswordFieldPasswordVerify)
so that the 'type' field discriminates: the 'PASSWORD_VERIFY' variant must
include verify: true (and any fields required for that case) while the
'PASSWORD' variant has verify?: boolean and optional passwordPolicy as before;
update usages that construct or consume PasswordField to handle the two variants
(using type narrowing) to preserve type safety around verify and passwordPolicy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d353af1d-2e88-4ad4-abfb-cdda4372942c

📥 Commits

Reviewing files that changed from the base of the PR and between 244dbf4 and 0159bb8.

📒 Files selected for processing (22)
  • .changeset/embed-password-policy-in-component.md
  • .nxignore
  • .opensource/forgerock-javascript-sdk
  • e2e/davinci-app/components/password.ts
  • e2e/davinci-app/main.ts
  • packages/davinci-client/api-report/davinci-client.api.md
  • packages/davinci-client/api-report/davinci-client.types.api.md
  • packages/davinci-client/src/lib/client.store.ts
  • packages/davinci-client/src/lib/client.types.ts
  • packages/davinci-client/src/lib/collector.types.test-d.ts
  • packages/davinci-client/src/lib/collector.types.ts
  • packages/davinci-client/src/lib/collector.utils.test.ts
  • packages/davinci-client/src/lib/collector.utils.ts
  • packages/davinci-client/src/lib/davinci.types.ts
  • packages/davinci-client/src/lib/davinci.utils.test.ts
  • packages/davinci-client/src/lib/mock-data/mock-form-fields.data.ts
  • packages/davinci-client/src/lib/mock-data/node.next.mock.ts
  • packages/davinci-client/src/lib/node.reducer.test.ts
  • packages/davinci-client/src/lib/node.reducer.ts
  • packages/davinci-client/src/lib/node.types.test-d.ts
  • packages/davinci-client/src/lib/node.types.ts
  • packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/davinci-client/src/lib/davinci.utils.test.ts
  • .nxignore
  • .opensource/forgerock-javascript-sdk
  • packages/davinci-client/src/lib/mock-data/node.next.mock.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • .changeset/embed-password-policy-in-component.md
  • packages/davinci-client/src/lib/node.types.ts
  • packages/davinci-client/src/lib/node.reducer.test.ts
  • packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts
  • packages/davinci-client/src/lib/collector.utils.test.ts
  • packages/davinci-client/src/lib/client.types.ts
  • packages/davinci-client/src/lib/mock-data/mock-form-fields.data.ts

@ryanbas21 ryanbas21 force-pushed the password-rules-toplevel branch from 0159bb8 to 49c8247 Compare April 22, 2026 19:49
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/davinci-client/src/lib/collector.utils.ts (1)

942-953: Nit: the policy ternary is always truthy.

returnSingleValueCollector sets passwordPolicy to {} (line 193-194) when the field lacks one, and the collector's output.passwordPolicy type is required, so policy here is always truthy and [] is never returned via the else branch. The rules already short-circuit on missing fields, so the guard is dead code and can be dropped for clarity — or, if you want the guard to remain meaningful, make ValidatedPasswordCollector['output']['passwordPolicy'] optional and stop defaulting to {} in the factory.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/collector.utils.ts` around lines 942 - 953,
The guard in returnPasswordPolicyValidator is dead because
ValidatedPasswordCollector.output.passwordPolicy is always an object (set to {}
by returnSingleValueCollector), so remove the ternary and simply apply
passwordPolicyRules to collector.output.passwordPolicy and the value (i.e.,
always run pipe( passwordPolicyRules, Arr.flatMap(rule =>
rule(collector.output.passwordPolicy, value)) )); this clarifies intent and
relies on the rules' own short-circuiting—alternatively, if you prefer keeping
the guard, make ValidatedPasswordCollector['output']['passwordPolicy'] optional
and stop defaulting it to {} in returnSingleValueCollector so the check becomes
meaningful.
e2e/davinci-app/components/password.ts (1)

81-111: Minor: reuse the input reference rather than re-querying.

input is already in scope from line 27, so formEl?.querySelector(\#${collectorKey}`)is an unnecessary DOM lookup (and a fragile one — any futurecollectorKeythat isn't a valid CSS ident would break#…). Same for error cleanup via .${collectorKey}-error`: caching the reference would avoid re-querying on every keystroke.

♻️ Proposed refactor
-  const inputEl = formEl?.querySelector(`#${collectorKey}`);
-  const shouldValidate = collector.type === 'ValidatedPasswordCollector' && !!validator;
-
-  inputEl?.addEventListener('input', (event: Event) => {
+  const shouldValidate = collector.type === 'ValidatedPasswordCollector' && !!validator;
+  let errorEl: HTMLUListElement | null = null;
+
+  input.addEventListener('input', (event: Event) => {
     const value = (event.target as HTMLInputElement).value;

     if (shouldValidate) {
       const result = validator(value);
       if (Array.isArray(result) && result.length) {
-        let errorEl = formEl?.querySelector<HTMLUListElement>(`.${collectorKey}-error`);
         if (!errorEl) {
           errorEl = document.createElement('ul');
           errorEl.className = `${collectorKey}-error`;
-          inputEl.after(errorEl);
+          input.after(errorEl);
         }
         const items = result.map((msg) => {
           const li = document.createElement('li');
           li.textContent = msg;
           return li;
         });
         errorEl.replaceChildren(...items);
         return;
       }
-      formEl?.querySelector(`.${collectorKey}-error`)?.remove();
+      errorEl?.remove();
+      errorEl = null;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/davinci-app/components/password.ts` around lines 81 - 111, The input DOM
is being re-queried inside the input event listener using
formEl?.querySelector(`#${collectorKey}`) and the error element is re-queried
for cleanup; instead reuse the existing input variable from outer scope (the one
created at line ~27) inside the listener and cache a single errorEl reference
(named e.g. errorEl) tied to `${collectorKey}-error` so you only create/query it
once when needed, update its children with replaceChildren, and call
errorEl?.remove() for cleanup instead of re-querying via formEl.querySelector;
keep the existing logic around shouldValidate, validator, and updater but
replace the repeated DOM queries with the cached input and cached errorEl
references to avoid fragile `#${collectorKey}` lookups and redundant work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/davinci-client/api-report/davinci-client.api.md`:
- Around line 150-157: The public API currently exposes
ValidatedPasswordCollector and requires output.passwordPolicy, but the intended
contract is PasswordVerifyCollector/PasswordVerifyField where passwordPolicy is
optional (omitted when absent); update the source type declarations so the
exported union uses PasswordVerifyCollector instead of
ValidatedPasswordCollector, change the field/type name to PasswordVerifyField if
necessary, make passwordPolicy optional on that collector/type, ensure any
discriminated union uses type: 'PasswordVerifyCollector' (or 'PASSWORD_VERIFY')
for switching, then regenerate the API report so davinci-client.api.md reflects
the corrected exported types and CollectorValueType behavior.

In `@packages/davinci-client/src/lib/collector.utils.ts`:
- Around line 901-910: The maxRepeatedCharactersRule currently uses total
character counts (via countChars) but must instead compute the longest
consecutive run of any character; update maxRepeatedCharactersRule to iterate
the input string (value), tracking currentChar and currentRun length, updating
maxRun when the character changes and at the end, then compare maxRun to
policy.maxRepeatedCharacters and return the same error message if exceeded;
remove the now-unused countChars helper (lines ~872–876) after this change.

In `@packages/davinci-client/src/lib/node.reducer.ts`:
- Around line 172-180: The switch handling for 'PASSWORD'/'PASSWORD_VERIFY'
currently uses field.passwordPolicy as the discriminator; change it to route
first on field.type so that case 'PASSWORD' always returns
returnPasswordCollector(field, idx) and case 'PASSWORD_VERIFY' always returns
returnValidatedPasswordCollector(field, idx) (or a dedicated verify-path
function) while using field.passwordPolicy only to populate or omit the policy
payload on the collector output; update the branch logic around
returnValidatedPasswordCollector and returnPasswordCollector to no longer flip
types based on passwordPolicy but instead to attach policy data conditionally
when building the PASSWORD_VERIFY collector.

---

Nitpick comments:
In `@e2e/davinci-app/components/password.ts`:
- Around line 81-111: The input DOM is being re-queried inside the input event
listener using formEl?.querySelector(`#${collectorKey}`) and the error element
is re-queried for cleanup; instead reuse the existing input variable from outer
scope (the one created at line ~27) inside the listener and cache a single
errorEl reference (named e.g. errorEl) tied to `${collectorKey}-error` so you
only create/query it once when needed, update its children with replaceChildren,
and call errorEl?.remove() for cleanup instead of re-querying via
formEl.querySelector; keep the existing logic around shouldValidate, validator,
and updater but replace the repeated DOM queries with the cached input and
cached errorEl references to avoid fragile `#${collectorKey}` lookups and
redundant work.

In `@packages/davinci-client/src/lib/collector.utils.ts`:
- Around line 942-953: The guard in returnPasswordPolicyValidator is dead
because ValidatedPasswordCollector.output.passwordPolicy is always an object
(set to {} by returnSingleValueCollector), so remove the ternary and simply
apply passwordPolicyRules to collector.output.passwordPolicy and the value
(i.e., always run pipe( passwordPolicyRules, Arr.flatMap(rule =>
rule(collector.output.passwordPolicy, value)) )); this clarifies intent and
relies on the rules' own short-circuiting—alternatively, if you prefer keeping
the guard, make ValidatedPasswordCollector['output']['passwordPolicy'] optional
and stop defaulting it to {} in returnSingleValueCollector so the check becomes
meaningful.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bc3dca3f-02a6-4b06-b008-bef7e60d04b7

📥 Commits

Reviewing files that changed from the base of the PR and between 0159bb8 and 49c8247.

📒 Files selected for processing (21)
  • .changeset/embed-password-policy-in-component.md
  • .nxignore
  • .opensource/forgerock-javascript-sdk
  • e2e/davinci-app/components/password.ts
  • e2e/davinci-app/main.ts
  • packages/davinci-client/api-report/davinci-client.api.md
  • packages/davinci-client/api-report/davinci-client.types.api.md
  • packages/davinci-client/src/lib/client.store.ts
  • packages/davinci-client/src/lib/client.types.ts
  • packages/davinci-client/src/lib/collector.types.test-d.ts
  • packages/davinci-client/src/lib/collector.types.ts
  • packages/davinci-client/src/lib/collector.utils.test.ts
  • packages/davinci-client/src/lib/collector.utils.ts
  • packages/davinci-client/src/lib/davinci.types.ts
  • packages/davinci-client/src/lib/davinci.utils.test.ts
  • packages/davinci-client/src/lib/mock-data/node.next.mock.ts
  • packages/davinci-client/src/lib/node.reducer.test.ts
  • packages/davinci-client/src/lib/node.reducer.ts
  • packages/davinci-client/src/lib/node.types.test-d.ts
  • packages/davinci-client/src/lib/node.types.ts
  • packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts
✅ Files skipped from review due to trivial changes (4)
  • .nxignore
  • packages/davinci-client/src/lib/davinci.utils.test.ts
  • packages/davinci-client/src/lib/mock-data/node.next.mock.ts
  • .opensource/forgerock-javascript-sdk
🚧 Files skipped from review as they are similar to previous changes (7)
  • e2e/davinci-app/main.ts
  • packages/davinci-client/src/lib/node.types.ts
  • .changeset/embed-password-policy-in-component.md
  • packages/davinci-client/src/lib/client.store.ts
  • packages/davinci-client/src/lib/collector.utils.test.ts
  • packages/davinci-client/src/lib/node.reducer.test.ts
  • packages/davinci-client/api-report/davinci-client.types.api.md

Comment thread packages/davinci-client/api-report/davinci-client.api.md Outdated
Comment on lines +901 to +910
const maxRepeatedCharactersRule: PasswordPolicyRule = (policy, value) => {
const max = policy.maxRepeatedCharacters;
if (max == null) return [];
const maxCount = pipe(
countChars(value),
(counts) => Array.from(counts.values()),
Arr.reduce(0, (acc, n) => (n > acc ? n : acc)),
);
return maxCount > max ? [`Password cannot repeat any character more than ${max} times`] : [];
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

PingOne password policy maxRepeatedCharacters — does it mean consecutive repeats or total occurrences of a character?

💡 Result:

PingOne's password policy setting maxRepeatedCharacters refers to the maximum number of consecutive repeated characters allowed in a password. It limits runs of the same character in sequence, such as no more than 2 or 3 identical characters in a row (e.g., "goood" with three 'o's would violate if max is 2, but "good-apple" with two 'o's separated is allowed). It does not limit total occurrences of a character across the password.

Citations:


🏁 Script executed:

# Check if file exists and get the current implementation
head -920 packages/davinci-client/src/lib/collector.utils.ts | tail -30

Repository: ForgeRock/ping-javascript-sdk

Length of output: 1085


🏁 Script executed:

# Search for countChars usage in the file
rg "countChars" packages/davinci-client/src/lib/collector.utils.ts -n

Repository: ForgeRock/ping-javascript-sdk

Length of output: 172


🏁 Script executed:

# Search for maxRepeatedCharactersRule usage
rg "maxRepeatedCharactersRule" packages/davinci-client/src/lib/collector.utils.ts -n

Repository: ForgeRock/ping-javascript-sdk

Length of output: 184


🏁 Script executed:

# Get context around lines 872-876 to see countChars definition
sed -n '872,876p' packages/davinci-client/src/lib/collector.utils.ts

Repository: ForgeRock/ping-javascript-sdk

Length of output: 274


maxRepeatedCharacters should count consecutive runs, not total occurrences.

The rule as written takes the max total count of any character across the entire password. PingOne's password policy documentation defines this constraint as the maximum number of times a character may appear consecutively — preventing values like "aaabbb" or "goood" (if max is 2). The current implementation wrongly rejects valid passwords and accepts invalid ones.

Concrete divergence with the current implementation, assuming maxRepeatedCharacters = 2:

  • "abcabcabc" is flagged (3 total as) even though no character repeats consecutively.
  • "aabaab" is flagged (4 total as) even though the longest run is 2, which is within the limit.
🐛 Proposed fix — measure the longest consecutive run
-const maxRepeatedCharactersRule: PasswordPolicyRule = (policy, value) => {
-  const max = policy.maxRepeatedCharacters;
-  if (max == null) return [];
-  const maxCount = pipe(
-    countChars(value),
-    (counts) => Array.from(counts.values()),
-    Arr.reduce(0, (acc, n) => (n > acc ? n : acc)),
-  );
-  return maxCount > max ? [`Password cannot repeat any character more than ${max} times`] : [];
-};
+const longestConsecutiveRun = (value: string): number => {
+  let longest = 0;
+  let current = 0;
+  let prev: string | undefined;
+  for (const ch of value) {
+    if (ch === prev) {
+      current += 1;
+    } else {
+      current = 1;
+      prev = ch;
+    }
+    if (current > longest) longest = current;
+  }
+  return longest;
+};
+
+const maxRepeatedCharactersRule: PasswordPolicyRule = (policy, value) => {
+  const max = policy.maxRepeatedCharacters;
+  if (max == null) return [];
+  return longestConsecutiveRun(value) > max
+    ? [`Password cannot repeat the same character more than ${max} times in a row`]
+    : [];
+};

After this change, countChars (lines 872–876) is unused and can be removed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const maxRepeatedCharactersRule: PasswordPolicyRule = (policy, value) => {
const max = policy.maxRepeatedCharacters;
if (max == null) return [];
const maxCount = pipe(
countChars(value),
(counts) => Array.from(counts.values()),
Arr.reduce(0, (acc, n) => (n > acc ? n : acc)),
);
return maxCount > max ? [`Password cannot repeat any character more than ${max} times`] : [];
};
const longestConsecutiveRun = (value: string): number => {
let longest = 0;
let current = 0;
let prev: string | undefined;
for (const ch of value) {
if (ch === prev) {
current += 1;
} else {
current = 1;
prev = ch;
}
if (current > longest) longest = current;
}
return longest;
};
const maxRepeatedCharactersRule: PasswordPolicyRule = (policy, value) => {
const max = policy.maxRepeatedCharacters;
if (max == null) return [];
return longestConsecutiveRun(value) > max
? [`Password cannot repeat the same character more than ${max} times in a row`]
: [];
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/collector.utils.ts` around lines 901 - 910,
The maxRepeatedCharactersRule currently uses total character counts (via
countChars) but must instead compute the longest consecutive run of any
character; update maxRepeatedCharactersRule to iterate the input string (value),
tracking currentChar and currentRun length, updating maxRun when the character
changes and at the end, then compare maxRun to policy.maxRepeatedCharacters and
return the same error message if exceeded; remove the now-unused countChars
helper (lines ~872–876) after this change.

Comment thread packages/davinci-client/src/lib/node.reducer.ts
Copy link
Copy Markdown
Collaborator

@cerebrl cerebrl left a comment

Choose a reason for hiding this comment

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

Initial scan, this looks good. One question on the type syntax, and I'd also like to ask what the api-report stuff is. Should I assume this relates to how you are generating the API migration guide and stuff?

Comment thread packages/davinci-client/src/lib/collector.types.ts
@ryanbas21
Copy link
Copy Markdown
Collaborator Author

Initial scan, this looks good. One question on the type syntax, and I'd also like to ask what the api-report stuff is. Should I assume this relates to how you are generating the API migration guide and stuff?

I demo'd this on Friday, but we use api-extractor in the generation / validation of exports. From a keeping it in git perspective, it shows the public api changes per PR in a digestable way as well. So it's two fold.

Comment thread .opensource/forgerock-javascript-sdk Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is this file?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I feel like we need to start breaking this code up into separate files. We don't have to do it in this PR, but wow, a near 1000 line file is kind of a lot.

Comment on lines +393 to +403
if (collectorToUpdate.type === 'ValidatedPasswordCollector') {
return returnPasswordPolicyValidator(collectorToUpdate);
}

if (collectorToUpdate.type === 'PasswordCollector') {
return handleUpdateValidateError(
'PasswordCollector cannot be validated; pass a ValidatedPasswordCollector',
'state_error',
log.error,
);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we flip this to be consistent with our success cases come after all error cases?

Comment thread packages/davinci-client/src/lib/collector.types.ts
Comment thread packages/davinci-client/src/lib/node.reducer.ts
Comment on lines +865 to +869
/**
* A single policy check: given the policy and a candidate value, produce zero or more
* human-readable error strings. Rules are pure and independent — new ones can be added
* by extending `passwordPolicyRules` below.
*/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

At the very least, can we pull all of these "rule" functions out into their own file?

@ryanbas21 ryanbas21 changed the title feat(davinci-client): embed password policy in PASSWORD_VERIFY collector feat(davinci-client): add ValidatedPasswordCollector with embedded password policy Apr 28, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
packages/davinci-client/src/lib/collector.utils.ts (1)

186-210: ⚠️ Potential issue | 🟠 Major

Don't materialize an empty passwordPolicy.

For PASSWORD_VERIFY without an embedded policy, the collector should omit output.passwordPolicy instead of forcing {}. The current fallback changes the contract from “optional when present” to “always present”, and downstream checks will treat “no policy” as a real policy object.

🛠️ Suggested change
-    const passwordPolicy =
-      'passwordPolicy' in field && field.passwordPolicy ? field.passwordPolicy : {};
+    const passwordPolicy = 'passwordPolicy' in field ? field.passwordPolicy : undefined;
     const verify = 'verify' in field ? field.verify === true : false;
     return {
       category: 'SingleValueCollector',
       error: error || null,
       type: collectorType,
@@
       output: {
         key: field.key,
         label: field.label,
         type: field.type,
         verify,
-        passwordPolicy,
+        ...(passwordPolicy ? { passwordPolicy } : {}),
       },

Please align the exported ValidatedPasswordCollector shape, reducer tests, and regenerated API report to passwordPolicy?: PasswordPolicy in the same change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/collector.utils.ts` around lines 186 - 210,
The current factory for collectorType 'ValidatedPasswordCollector' always
materializes passwordPolicy as {} (via the passwordPolicy fallback), but it
should omit output.passwordPolicy when none is provided; change the logic in the
ValidatedPasswordCollector branch so that you do not set passwordPolicy =
{}—instead detect presence ('passwordPolicy' in field && field.passwordPolicy)
and only add output.passwordPolicy when present (or set it to undefined/omit the
key), update the ValidatedPasswordCollector/output shape accordingly, and then
run/update the reducer tests and regenerate the API report to reflect
passwordPolicy?: PasswordPolicy.
packages/davinci-client/src/lib/password-policy.rules.ts (1)

19-23: ⚠️ Potential issue | 🟠 Major

Count consecutive runs, not total character frequency.

PingOne documents maxRepeatedCharacters as a cap on consecutive repeats; e.g. good-apple is acceptable while goood-appple is not. The current countChars() / maxCount path measures total occurrences across the whole password, so it will reject valid values like abcabcabc. (docs.pingidentity.com)

🐛 Proposed fix
-const countChars = (value: string): ReadonlyMap<string, number> => {
-  const counts = new Map<string, number>();
-  for (const ch of value) counts.set(ch, (counts.get(ch) ?? 0) + 1);
-  return counts;
-};
+const longestConsecutiveRun = (value: string): number => {
+  let longest = 0;
+  let current = 0;
+  let prev: string | undefined;
+
+  for (const ch of value) {
+    current = ch === prev ? current + 1 : 1;
+    prev = ch;
+    if (current > longest) longest = current;
+  }
+
+  return longest;
+};

 const maxRepeatedCharactersRule: PasswordPolicyRule = (policy, value) => {
   const max = policy.maxRepeatedCharacters;
   if (max == null) return [];
-  const maxCount = pipe(
-    countChars(value),
-    (counts) => Array.from(counts.values()),
-    Arr.reduce(0, (acc, n) => (n > acc ? n : acc)),
-  );
-  return maxCount > max ? [`Password cannot repeat any character more than ${max} times`] : [];
+  return longestConsecutiveRun(value) > max
+    ? [`Password cannot repeat any character more than ${max} times in a row`]
+    : [];
 };

Also applies to: 48-56

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/password-policy.rules.ts` around lines 19 -
23, The logic in countChars incorrectly tallies total occurrences rather than
consecutive repeats; update the implementation used by the maxCount check to
compute the maximum consecutive run length per character instead of total
frequency. Replace or modify countChars (or introduce a new helper like
getMaxConsecutiveRuns) to iterate the string once, track the current run and
update the per-character max run (e.g., currentChar and currentRun -> update map
with Math.max(existing, currentRun) when char changes), and then use those
max-consecutive values when enforcing maxRepeatedCharacters in the password rule
code paths that reference countChars / maxCount.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/davinci-client/src/lib/collector.utils.ts`:
- Around line 186-210: The current factory for collectorType
'ValidatedPasswordCollector' always materializes passwordPolicy as {} (via the
passwordPolicy fallback), but it should omit output.passwordPolicy when none is
provided; change the logic in the ValidatedPasswordCollector branch so that you
do not set passwordPolicy = {}—instead detect presence ('passwordPolicy' in
field && field.passwordPolicy) and only add output.passwordPolicy when present
(or set it to undefined/omit the key), update the
ValidatedPasswordCollector/output shape accordingly, and then run/update the
reducer tests and regenerate the API report to reflect passwordPolicy?:
PasswordPolicy.

In `@packages/davinci-client/src/lib/password-policy.rules.ts`:
- Around line 19-23: The logic in countChars incorrectly tallies total
occurrences rather than consecutive repeats; update the implementation used by
the maxCount check to compute the maximum consecutive run length per character
instead of total frequency. Replace or modify countChars (or introduce a new
helper like getMaxConsecutiveRuns) to iterate the string once, track the current
run and update the per-character max run (e.g., currentChar and currentRun ->
update map with Math.max(existing, currentRun) when char changes), and then use
those max-consecutive values when enforcing maxRepeatedCharacters in the
password rule code paths that reference countChars / maxCount.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e2ff15f4-f5a3-49ec-8a62-387bd227f850

📥 Commits

Reviewing files that changed from the base of the PR and between 49c8247 and 78198bd.

📒 Files selected for processing (10)
  • .changeset/embed-password-policy-in-component.md
  • packages/davinci-client/api-report/davinci-client.api.md
  • packages/davinci-client/api-report/davinci-client.types.api.md
  • packages/davinci-client/src/lib/client.store.ts
  • packages/davinci-client/src/lib/collector.utils.test.ts
  • packages/davinci-client/src/lib/collector.utils.ts
  • packages/davinci-client/src/lib/mock-data/mock-form-fields.data.ts
  • packages/davinci-client/src/lib/node.reducer.test.ts
  • packages/davinci-client/src/lib/node.reducer.ts
  • packages/davinci-client/src/lib/password-policy.rules.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • .changeset/embed-password-policy-in-component.md
  • packages/davinci-client/src/lib/client.store.ts
  • packages/davinci-client/src/lib/collector.utils.test.ts

nx-cloud[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Collaborator

@cerebrl cerebrl left a comment

Choose a reason for hiding this comment

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

Another scan through the PR.

* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
import { Array as Arr, Option, pipe } from 'effect';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this going to pull in more of the Effect library? Historically, we only imported Micro, yeah? In order to keep this bundle as small as possible, I'd like to make sure this is really necessary.

Copy link
Copy Markdown
Collaborator Author

@ryanbas21 ryanbas21 Apr 29, 2026

Choose a reason for hiding this comment

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

No, it won't. Micro uses Option/Array already

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this is ok because Option and Array are basic data types that don't affect the bundle size.

https://effect.website/docs/micro/new-users/

Bundle Size

Utilizing major Effect modules beyond basic data modules like Option, Either, Array, will incorporate the Effect runtime into your bundle, negating the benefits of Micro.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Cool, I'm good with this then. Just wanted to check. Thanks you two!

Comment on lines +175 to +176
case 'PASSWORD_VERIFY': {
return returnValidatedPasswordCollector(field, idx);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this correct? I thought we were driving whether the collector was ValidatedPasswordCollector or not by the presence of policies, yeah?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@ryanbas21 can you copy paste what you're referring to here? The link is taking me to the top of your PR for some reason.

If PASSWORD_VERIFY does not have any policies then should it fall back to a PasswordCollector?

expect((result[0] as ValidatedPasswordCollector).output.passwordPolicy).toEqual({});
});

it('should produce PasswordCollector when a PASSWORD field carries a policy (policy ignored)', () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

should produce PasswordCollector when a PASSWORD field carries a policy (policy ignored)

huh?

@ryanbas21 ryanbas21 force-pushed the password-rules-toplevel branch 2 times, most recently from f9533cb to 11d4052 Compare April 29, 2026 21:41
nx-cloud[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Collaborator

@ancheetah ancheetah left a comment

Choose a reason for hiding this comment

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

Looks alright to me. Left a comment asking for clarification.

Comment on lines +175 to +176
case 'PASSWORD_VERIFY': {
return returnValidatedPasswordCollector(field, idx);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@ryanbas21 can you copy paste what you're referring to here? The link is taking me to the top of your PR for some reason.

If PASSWORD_VERIFY does not have any policies then should it fall back to a PasswordCollector?

* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
import { Array as Arr, Option, pipe } from 'effect';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this is ok because Option and Array are basic data types that don't affect the bundle size.

https://effect.website/docs/micro/new-users/

Bundle Size

Utilizing major Effect modules beyond basic data modules like Option, Either, Array, will incorporate the Effect runtime into your bundle, negating the benefits of Micro.

@ryanbas21
Copy link
Copy Markdown
Collaborator Author

Looks alright to me. Left a comment asking for clarification.

Screenshot 2026-04-30 at 11 54 57 AM

@ryanbas21 ryanbas21 force-pushed the password-rules-toplevel branch from 11d4052 to 9ba1b33 Compare April 30, 2026 19:15
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/davinci-client/src/lib/collector.types.ts (1)

21-28: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

SingleValueCollector<T> no longer matches the public type union.

SingleValueCollectorTypes now includes the password variants, but SingleValueCollector<T> still expands them into the generic value/no-value shapes. That makes SingleValueCollector<'PasswordCollector'> and SingleValueCollector<'ValidatedPasswordCollector'> type to the wrong structure.

Please either keep the password variants out of this generic or add dedicated branches so the exported API stays sound.

Also applies to: 179-181

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/collector.types.ts` around lines 21 - 28, The
exported union SingleValueCollectorTypes now includes 'PasswordCollector' and
'ValidatedPasswordCollector' but the generic mapped type SingleValueCollector<T>
still resolves only to the generic value/no-value shapes, causing
SingleValueCollector<'PasswordCollector'> to have the wrong structure; to fix,
update SingleValueCollector<T> to include explicit branches for
'PasswordCollector' and 'ValidatedPasswordCollector' that map to the correct
password-specific shape (or alternatively remove those two variants from
SingleValueCollectorTypes), and apply the same change to the related
generic/type mapping used around the other occurrence referenced (the similar
mapping at the later block).
🧹 Nitpick comments (1)
packages/davinci-client/src/lib/collector.utils.ts (1)

472-493: ⚡ Quick win

Constrain factory inputs to their password field discriminants.

These helpers currently accept both PASSWORD and PASSWORD_VERIFY. Narrowing each function’s input type will prevent accidental cross-routing at compile time.

Type-safe signature refinement
-export function returnPasswordCollector(field: PasswordField, idx: number): PasswordCollector {
+export function returnPasswordCollector(
+  field: Extract<PasswordField, { type: 'PASSWORD' }>,
+  idx: number,
+): PasswordCollector {
   return returnSingleValueCollector(field, idx, 'PasswordCollector') as PasswordCollector;
 }

 export function returnValidatedPasswordCollector(
-  field: PasswordField,
+  field: Extract<PasswordField, { type: 'PASSWORD_VERIFY' }>,
   idx: number,
 ): ValidatedPasswordCollector {
   return returnSingleValueCollector(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/davinci-client/src/lib/collector.utils.ts` around lines 472 - 493,
The two factory functions accept the broader PasswordField union; narrow their
first-parameter types to the specific discriminants so they only accept the
correct variant: change returnPasswordCollector(field: PasswordField, ...) to
accept the PasswordField variant whose type is 'PASSWORD' (e.g. PasswordField &
{ type: 'PASSWORD' } or the existing concrete interface for PASSWORD fields),
and change returnValidatedPasswordCollector(field: PasswordField, ...) to accept
only the 'PASSWORD_VERIFY' variant (e.g. PasswordField & { type:
'PASSWORD_VERIFY' } or its concrete interface); keep the idx parameter and
return types the same (PasswordCollector and ValidatedPasswordCollector) so
callers that pass wrong discriminants will fail at compile time.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/davinci-client/src/lib/collector.utils.test.ts`:
- Around line 1266-1267: Update the stale test comment to reflect current
reducer routing: change the description that says the reducer selects
returnPasswordCollector when a field has no policy to state that the reducer now
routes PASSWORD_VERIFY to returnValidatedPasswordCollector regardless of policy;
mention that the test specifically exercises the factory's defensive fallback
for callers bypassing the reducer (referencing PASSWORD_VERIFY,
returnValidatedPasswordCollector, returnPasswordCollector, and the factory code)
so readers understand why the fallback test remains necessary.

In `@packages/davinci-client/src/lib/davinci.types.ts`:
- Around line 85-90: Update the PasswordField docblock to reflect current
runtime routing: state that the server sets the `type` to `PASSWORD` or
`PASSWORD_VERIFY` and that collector selection is driven by the `type` field at
runtime (not by presence of `passwordPolicy`), and mention how
`PasswordCollector` vs `ValidatedPasswordCollector` are resolved now; edit the
comment near the PasswordField/type definition to remove the misleading
"policy-driven" wording and explicitly describe the `PASSWORD` vs
`PASSWORD_VERIFY` behavior.

---

Outside diff comments:
In `@packages/davinci-client/src/lib/collector.types.ts`:
- Around line 21-28: The exported union SingleValueCollectorTypes now includes
'PasswordCollector' and 'ValidatedPasswordCollector' but the generic mapped type
SingleValueCollector<T> still resolves only to the generic value/no-value
shapes, causing SingleValueCollector<'PasswordCollector'> to have the wrong
structure; to fix, update SingleValueCollector<T> to include explicit branches
for 'PasswordCollector' and 'ValidatedPasswordCollector' that map to the correct
password-specific shape (or alternatively remove those two variants from
SingleValueCollectorTypes), and apply the same change to the related
generic/type mapping used around the other occurrence referenced (the similar
mapping at the later block).

---

Nitpick comments:
In `@packages/davinci-client/src/lib/collector.utils.ts`:
- Around line 472-493: The two factory functions accept the broader
PasswordField union; narrow their first-parameter types to the specific
discriminants so they only accept the correct variant: change
returnPasswordCollector(field: PasswordField, ...) to accept the PasswordField
variant whose type is 'PASSWORD' (e.g. PasswordField & { type: 'PASSWORD' } or
the existing concrete interface for PASSWORD fields), and change
returnValidatedPasswordCollector(field: PasswordField, ...) to accept only the
'PASSWORD_VERIFY' variant (e.g. PasswordField & { type: 'PASSWORD_VERIFY' } or
its concrete interface); keep the idx parameter and return types the same
(PasswordCollector and ValidatedPasswordCollector) so callers that pass wrong
discriminants will fail at compile time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 765fd4fc-fa76-4ac8-a4e8-ac9f2a553249

📥 Commits

Reviewing files that changed from the base of the PR and between 78198bd and 9ba1b33.

📒 Files selected for processing (21)
  • .changeset/embed-password-policy-in-component.md
  • e2e/davinci-app/components/password.ts
  • e2e/davinci-app/main.ts
  • packages/davinci-client/api-report/davinci-client.api.md
  • packages/davinci-client/api-report/davinci-client.types.api.md
  • packages/davinci-client/src/lib/client.store.ts
  • packages/davinci-client/src/lib/client.types.ts
  • packages/davinci-client/src/lib/collector.types.test-d.ts
  • packages/davinci-client/src/lib/collector.types.ts
  • packages/davinci-client/src/lib/collector.utils.test.ts
  • packages/davinci-client/src/lib/collector.utils.ts
  • packages/davinci-client/src/lib/davinci.types.ts
  • packages/davinci-client/src/lib/davinci.utils.test.ts
  • packages/davinci-client/src/lib/mock-data/mock-form-fields.data.ts
  • packages/davinci-client/src/lib/mock-data/node.next.mock.ts
  • packages/davinci-client/src/lib/node.reducer.test.ts
  • packages/davinci-client/src/lib/node.reducer.ts
  • packages/davinci-client/src/lib/node.types.test-d.ts
  • packages/davinci-client/src/lib/node.types.ts
  • packages/davinci-client/src/lib/password-policy.rules.ts
  • packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/davinci-client/src/lib/mock-data/node.next.mock.ts
  • e2e/davinci-app/main.ts
  • packages/davinci-client/src/lib/node.reducer.test.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/davinci-client/src/lib/davinci.utils.test.ts
  • packages/davinci-client/src/lib/node.types.test-d.ts
  • .changeset/embed-password-policy-in-component.md
  • packages/davinci-client/src/lib/client.store.ts
  • packages/davinci-client/src/lib/mock-data/mock-form-fields.data.ts
  • packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts
  • packages/davinci-client/src/lib/password-policy.rules.ts
  • e2e/davinci-app/components/password.ts

Comment thread packages/davinci-client/src/lib/collector.utils.test.ts Outdated
Comment thread packages/davinci-client/src/lib/davinci.types.ts
nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

@ryanbas21 ryanbas21 force-pushed the password-rules-toplevel branch 2 times, most recently from 9e2fce3 to 66eb586 Compare April 30, 2026 20:07
nx-cloud[bot]

This comment was marked as outdated.

@ryanbas21 ryanbas21 force-pushed the password-rules-toplevel branch from 66eb586 to df0ad30 Compare April 30, 2026 20:29
…ssword policy

DV-16053: PingOne moves password policy from the response root onto the
PASSWORD_VERIFY field. Surfaces a typed ValidatedPasswordCollector that
exposes the embedded policy and validates input against it, while leaving
plain PASSWORD fields on the simpler PasswordCollector path.

- Splits PASSWORD vs PASSWORD_VERIFY into PasswordCollector and
  ValidatedPasswordCollector; reducer discriminates by field.type.
- New password-policy.rules.ts with pure rule functions (length,
  minUniqueCharacters, maxRepeatedCharacters, minCharacters) and
  returnPasswordPolicyValidator() for use by client.validate().
- client.validate() routes ValidatedPasswordCollector through the policy
  validator and rejects plain PasswordCollector with a typed error.
- Updates collector type plumbing, mock data, sample app rendering, and
  regenerates API reports.
@ryanbas21 ryanbas21 force-pushed the password-rules-toplevel branch from df0ad30 to d8648fb Compare April 30, 2026 20:30
Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud Bot left a comment

Choose a reason for hiding this comment

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

Nx Cloud is proposing a fix for your failed CI:

We fixed a routing bug in the node reducer where PASSWORD and PASSWORD_VERIFY fields were dispatched based on the presence of field.passwordPolicy rather than field.type. This caused PASSWORD_VERIFY fields without a policy to incorrectly produce a PasswordCollector, and PASSWORD fields with a policy to incorrectly produce a ValidatedPasswordCollector. Splitting the cases to route strictly by field.type aligns the implementation with the PR intent and fixes both failing tests.

Warning

We could not verify this fix.

diff --git a/packages/davinci-client/src/lib/node.reducer.ts b/packages/davinci-client/src/lib/node.reducer.ts
index d631c0cbc..922e655cb 100644
--- a/packages/davinci-client/src/lib/node.reducer.ts
+++ b/packages/davinci-client/src/lib/node.reducer.ts
@@ -176,11 +176,11 @@ export const nodeCollectorReducer = createReducer(initialCollectorValues, (build
                 // Intentional fall-through
                 return returnObjectSelectCollector(field, idx);
               }
-              case 'PASSWORD':
+              case 'PASSWORD': {
+                return returnPasswordCollector(field, idx);
+              }
               case 'PASSWORD_VERIFY': {
-                return field.passwordPolicy
-                  ? returnValidatedPasswordCollector(field, idx)
-                  : returnPasswordCollector(field, idx);
+                return returnValidatedPasswordCollector(field, idx);
               }
               case 'PHONE_NUMBER': {
                 const prefillData = data as PhoneNumberOutputValue;

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally YMRJ-yYAU

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

Copy link
Copy Markdown
Collaborator

@cerebrl cerebrl left a comment

Choose a reason for hiding this comment

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

I think this looks good. I do have a question, but I'm not sure about it, so I'm asking you you all feel about it. Should passwordPolicy be in the input object or the output object?

Comment on lines +416 to +419
if (
collectorToUpdate.type !== 'ValidatedPasswordCollector' &&
!('validation' in collectorToUpdate.input)
) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This makes me think ... should the policies property be on the input or the output? The case for being on the input object is that it relates directly to the input value. But, these policies can also be rendered to screen, so it's kind of like output too. Hmm ...

* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
import { Array as Arr, Option, pipe } from 'effect';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Cool, I'm good with this then. Just wanted to check. Thanks you two!

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants