Skip to content

feat(ENG-823): setup and config field validation - #183

Open
chandrajeet-singh wants to merge 11 commits into
StackOneHQ:mainfrom
chandrajeet-singh:feat(eng-821)/setup-and-config-field-validation
Open

feat(ENG-823): setup and config field validation#183
chandrajeet-singh wants to merge 11 commits into
StackOneHQ:mainfrom
chandrajeet-singh:feat(eng-821)/setup-and-config-field-validation

Conversation

@chandrajeet-singh

@chandrajeet-singhchandrajeet-singh commented Jul 8, 2026

Copy link
Copy Markdown

Context

Standalone-hub half of the ENG-823 setup/config field-validation work (RFC step 7; sibling PRs: connect#1304 — canonical registry + build-time checks, unified-cloud#3212 — dashboard/widget, unified-cloud-api#8612 — server observe telemetry). The branch carries the parent-ticket name (eng-821); commits and this title use the implementation ticket ENG-823 — same pairing as every sibling PR in the chain.

What changes for users

  • Client-side field validation: setup/config fields carrying Falcon validation: (pattern or format) or legacy V2 rules (html-pattern, domain) are enforced in the hub form via the Zod schema — error message on the field, Connect gated until valid. Saved-secret placeholders are exempt so reconnect is never blocked.
  • Fail-open paths (deliberate): an unknown format, an uncompilable pattern, or a pattern flagged by the ReDoS lint renders the field without validation (today's behaviour) rather than broken or hanging — each path is loud (console.warn / lint) but never blocks a customer.
  • Count-only friction metric: stackone-hub:field-validation-failed DOM CustomEvent (connector + field + rule kind — never the value), deduped once per field per form session. No listener ships in this repo by design (customer-embedded package, no analytics dependency).
  • CI gate: npm test now runs the format conformance gate plus the vitest suite (previously the spec file never executed anywhere).

Known gaps (recorded deliberately)

  • Legacy domain interpolation (pre-existing): the historical .*<pattern>.com* rule interpolates the author pattern unescaped; preserved byte-for-byte for no-V2-regression, now additionally guarded by the ReDoS lint in compileRegex. Tightening the semantics is a V2-retirement concern, not this PR.
  • Vector-check blind spot: the conformance gate's vectors and pinned canonical sources are hub-local snapshots — they catch pattern↔vector drift and language-identical source drift, but an upstream format added after the snapshot is not caught automatically and cannot be: connect is a private repo, so no unauthenticated live comparison is possible from this public repo's CI. Adding a format upstream requires a manual sync of the registry copy, vectors and pinned sources together (documented in the gate's header).

Test plan

npm test — format conformance gate (accept/reject vectors, reverse coverage, pinned canonical sources, live-canonical comparison when reachable) + 19 vitest specs covering the Falcon/legacy resolvers, saved-secret carve-out, recorder lifetime across schema rebuilds, and the ReDoS guard (both exponential classes return in <1s). tsc --noEmit and biome check clean.

@chandrajeet-singhchandrajeet-singh changed the title feat(eng-821): setup and config field validationfeat(ENG-821): setup and config field validationJul 8, 2026
@chandrajeet-singh
chandrajeet-singh marked this pull request as ready for review July 10, 2026 11:05
CopilotAI review requested due to automatic review settings July 10, 2026 11:05

@claudeclaudeBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds client-side validation support for connector setup/config fields in the integration picker, including Falcon-style validation (format/pattern) while preserving legacy (v2) connector validation behavior. It also adds a small CI-style guard to keep a local copy of format regexes aligned with the canonical @stackone/core accept/reject vectors.

Changes:

  • Extend ConnectorConfigField.validation to support both legacy and Falcon validation contracts (including format-based validation).
  • Add a local FORMAT_PATTERNS registry and rule-resolution helpers to apply validations in the Zod schema builder.
  • Add a npm test script that runs a vector-check script to prevent regex drift.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

FileDescription
src/modules/integration-picker/utils/zodSchema.tsIntroduces format/pattern validation resolution and applies the resolved regex rule to Zod field schemas.
src/modules/integration-picker/types.tsAdds typed validation contracts for legacy vs Falcon connectors and updates ConnectorConfigField.validation.
scripts/check-format-vectors.tsAdds a script to validate the local format regex registry against known accept/reject vectors.
package.jsonUpdates npm test to run the new vector-check script via tsx.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/modules/integration-picker/utils/zodSchema.ts
Comment threadsrc/modules/integration-picker/utils/zodSchema.ts
Comment threadsrc/modules/integration-picker/utils/zodSchema.ts
Comment threadscripts/check-format-vectors.ts
@chandrajeet-singh
chandrajeet-singhforce-pushed the feat(eng-821)/setup-and-config-field-validation branch from d920972 to f8ac050CompareJuly 22, 2026 14:11
@chandrajeet-singhchandrajeet-singh changed the title feat(ENG-821): setup and config field validationfeat(ENG-823): setup and config field validationJul 22, 2026

@StuBehanStuBehan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed against RFC 2026-037 (step 7). The step-7 scope itself looks right: the discriminated union matches the RFC's branch shapes, the two resolvers are properly independent, the Falcon fallback-message order matches the spec table exactly, and I verified the legacy path is behaviour-preserving — the "No V2 regression" AC holds. I also diffed FORMAT_PATTERNS, the vectors, and FormatName against connect@packages/core/src/connector/ and all three copies are byte-faithful, so the D3 copy is correct as of today.

Verified locally on a clean install of the PR head: npm run build, npm run lint, npm test, npx biome check scripts/, and tsc -p tsconfig.json all pass.

Four things I'd want resolved before merge:

  1. D3's CI guard doesn't actually run. The RFC says three separate times that a drifted copy "fails that repo's CI" via the hub's npm test, and both new comment blocks repeat it. But .github/workflows/node-ci.yml runs npm cinpm run buildnpm run lint and never npm test. The check works — it's just never executed. One line in the workflow.

  2. The guard also can't catch the drift it exists for. Picking up @copilot's point on the script: the hub's vector table is a local copy iterated by key, so a format added to core with no corresponding hub update produces no failure here. That matters more than it looks, because the fall-through in resolveFalconRule is silent — an unrecognised format yields no rule at all, so a field the author declared as validated renders completely unvalidated. Confirmed: format: 'hostname' accepts "literally anything". D3 notes this mechanism exists precisely because "the copies had genuinely drifted three ways… while every stale vector copy still passed CI" — this is that hole again.

  3. Saved secrets will block reconnect as soon as a connector uses this. The RFC's risks table says "Pre-existing accounts not re-validated — existing accounts unaffected. Validation runs only as the user types." That doesn't hold on this surface: secret fields are pre-filled with the redacted sentinel, and RHF computes isValid eagerly from defaultValues, so validation runs before the user touches anything. Details inline — I think this is an RFC assumption that needs correcting, not just a code fix.

  4. The step-9 telemetry probably shouldn't be in this PR. As implemented it can't satisfy step 9 (no connector identifier, dedupe doesn't hold, no collector), and the RFC's rollout ordering has 9 trailing 1–8. Splitting it keeps step 7 shippable now — the frontends have to be deployed before step 8 authors anything.

Nits: branch says eng-821, title and commit say ENG-823. PR body is empty — worth linking the RFC and noting this is step 7. The RFC also describes this surface as mode: 'onChange' twice while it's actually onTouched — your comment has it right, so the RFC is the stale one.

Comment threadsrc/modules/integration-picker/utils/zodSchema.ts
Comment threadsrc/modules/integration-picker/utils/zodSchema.ts Outdated
Comment threadsrc/modules/integration-picker/utils/zodSchema.ts Outdated
Comment threadsrc/modules/integration-picker/utils/zodSchema.ts
Comment threadsrc/modules/integration-picker/types.ts

@StuBehanStuBehan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Switching my earlier review to request-changes to gate the merge — full detail is already in the threads, nothing new here.

The two I'd want fixed first are the saved-secret reconnect blocker and the per-keystroke telemetry event. Neither breaks anything on merge, since no connector authors setup/config validation: yet — but the RFC's rollout ordering has all three frontends deployed before step 8 authors any, so there's no natural forcing function to come back to them. Easier to fix now than to find it live behind a customer's embedded hub.

Happy to be talked out of the telemetry one specifically if you'd rather split step 9 out and land step 7 as-is — that resolves it too.

@chandrajeet-singh
chandrajeet-singhforce-pushed the feat(eng-821)/setup-and-config-field-validation branch from 953ff37 to 56aa5c4CompareJuly 30, 2026 12:28
@chandrajeet-singh

Copy link
Copy Markdown
Author

Thanks @StuBehan — addressed the threads:

  • D3 CI guard:npm test now runs in node-ci.yml, and check-format-vectors.ts has the reverse-coverage assertion (a registry format with no vectors now fails CI) — closes the drift hole. Synced the datetime vectors too.
  • Saved-secret reconnect blocker:testWithMetric short-circuits isSecretPlaceholder(val), so a pre-filled redacted secret no longer fails validation / gates Connect / emits a spurious event. Flagged the RFC "existing accounts unaffected" risk row for amendment.
  • Unknown format: now console.warns before failing open, instead of silently rendering unvalidated.
  • Regex try/catch: added compileRegex — an uncompilable pattern degrades to no rule instead of throwing during render and taking down the hub via the error boundary.
  • && val guard: expanded the comment on why it's load-bearing (zod-4 accumulation).
  • Telemetry: the event now carries the connector id and dedupes (the fields memo is stable per keystroke). Happy to split step 9 into its own PR and land step 7 as-is if you'd prefer — your call.

Added tests for the secret short-circuit, unknown-format fail-open, uncompilable-pattern degrade, and widened datetime. Ready for another pass 🙏

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@StuBehanStuBehan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The isSecretPlaceholder guard is the right fix for the reconnect case, the console.warn on unknown format closes the silent fail-open, and the && val comment is now clear about why it is load-bearing. Thanks for those.

Reopening one thread: the keystroke dedupe is still broken. It was marked resolved, but the code did not change and the comment was reworded to assert the behaviour we do not have. Detail inline. Also, this is currently conflicting with main, so it needs a rebase before it can go in at step 3 of the chain.

Comment threadsrc/modules/integration-picker/utils/zodSchema.ts Outdated
Chandrajeet Singhand others added 2 commits August 10, 2026 15:29
- Hoist the failure recorder out of createFormSchema into
IntegrationFields (useMemo keyed on connectorKey) and pass it in:
the schema is rebuilt on every keystroke (watch -> onChange(formData)
-> new fields identity), so a recorder owned by the build reset its
per-field dedupe each rebuild and dispatched one event per keystroke.
Mirrors the unified-cloud DynamicForm and embedded-widget wiring.
- Regression tests pin the contract: one event per field across schema
rebuilds, and the count-only event detail (connector + field +
ruleKind, never the value).
- Declare vitest and chain `vitest run` into npm test — the spec file
previously never executed anywhere (npm test only ran the vector
check, and vitest was not a declared dependency).
- Note on ConnectorConfigField.validation that core's select-branch
`validation?: never` constraint does not survive this flat copy
(build-time rejection keeps exposure nil).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chandrajeet-singh
chandrajeet-singhforce-pushed the feat(eng-821)/setup-and-config-field-validation branch from 3de3fa6 to 8e3708eCompareAugust 10, 2026 10:04
npm 11 locally omitted vite's optional peer yaml@2 resolution that
node-ci's npm 10 requires, so `npm ci` failed with "Missing: yaml@2.9.0
from lock file". Regenerated with npm@10.9.2 and verified with
`npm ci --dry-run`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@jerannjerann 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.

Adversarial pre-review, run alongside the related connect#1304 / unified-cloud#3212 / unified-cloud-api#8612 cluster. hub isn't one of the repos the review canon covers, so this is entirely independent probing rather than canon-check matches — all 4 findings below are reproduced, not theoretical.

Needs a change

  • FORMAT_PATTERNS.url reintroduces the exact ReDoS pattern connect#1304 just fixed for this exact reason — reproduced a 700ms+ hang on a 40k-character input. src/modules/integration-picker/utils/zodSchema.ts:19 (commented inline)
  • compileRegex's try/catch only catches invalid regex syntax at construction time, not catastrophic backtracking at match time, despite the comment above it claiming it "guard[s] here too" — reproduced a 15s+ hang on ^(a+)+$. src/modules/integration-picker/utils/zodSchema.ts:41 (commented inline)
  • uri accepts trailing-garbage values connect#1304's canonical pattern rejects, and this repo's own conformance-vector list is missing exactly the reject cases that would catch it. src/modules/integration-picker/utils/zodSchema.ts:20 (commented inline)
  • The new npm test CI gate resolves tsx via npx -y rather than an actual pinned dependency — confirmed tsx only appears as an optional peer dependency of a vitest sub-dependency in the lockfile, not as an installed package, so it's fetched from the registry outside npm ci's integrity checks on every run. package.json:23 (commented inline)

Worth knowing
This PR's regex/type mirror is checked directly against connect#1304's actual diff (still open): 4 of 6 pieces match exactly, but url and uri have already drifted — see the inline comments. Since #1304 can still change before merging, worth re-diffing this file against it right before this PR merges, not just at authoring time.

— eng-review · canon c225d039 (hub not in canon — these are all independent findings) · automated pre-review, not a human review

Comment threadsrc/modules/integration-picker/utils/zodSchema.ts Outdated
Comment threadsrc/modules/integration-picker/utils/zodSchema.ts
Comment threadsrc/modules/integration-picker/utils/zodSchema.ts Outdated
Comment threadpackage.json Outdated
chandrajeet-singhand others added 2 commits August 11, 2026 16:08
…ning
connect#1304's round-2 review rewrote the canonical email pattern with a
lookahead (the overlapping domain/TLD tail backtracks quadratically, and
this copy runs per keystroke). Language-identical, so the vector
conformance check cannot catch this drift — synced manually.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Sync url (linear disjoint-boundary form) and uri (rejects trailing
garbage) with the canonical registry — the email sync landed earlier
but these two had drifted; add the uri reject vectors that would have
caught it.
- Port hasCatastrophicBacktrackingRisk from connect-sdk as a local copy
and gate compileRegex on it: the try/catch only ever caught
construction-time SyntaxErrors, while a stored `^(a+)+$` hung the tab
15s+ at match time. Fail open (rule skipped, no validation) with
tests pinning both exponential classes return quickly.
- Correct the compileRegex comment that claimed the guard covered the
backtracking hazard it did not.
- Pin tsx as a devDependency and drop `npx -y` from npm test, so CI
resolves it from the lockfile under npm ci integrity checks instead
of fetching it unpinned from the registry per run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chandrajeet-singh

Copy link
Copy Markdown
Author

Round-2 findings all addressed in 0750906 (inline replies on each thread):

  • url/uri drift from connect#1304: synced to the canonical hardened forms (linear url, trailing-garbage-rejecting uri) and added the uri reject vectors that would have caught the drift in CI. The email sync had landed earlier; these two were missed.
  • compileRegex's false ReDoS claim: fixed with a real guard, not a reworded comment — hasCatastrophicBacktrackingRisk is ported from connect-sdk as a local copy (same no-@stackone-deps model as the FORMAT_PATTERNS copy) and gates compileRegex, failing open. Tests pin that ^(a+)+$ and (a|a)+ are dropped in <1s instead of hanging the tab, and that safe legacy rules still apply.
  • Unpinned tsx in CI: now a pinned devDependency resolved from the lockfile; npx -y dropped from npm test.

On the "worth knowing" note about re-diffing against connect#1304 before merge: agreed — the registry copy, the vectors, and now the lint copy all name their canonical sources, and I'll re-diff all three if #1304 changes again before this merges. All 19 tests + the vector check pass, tsc and biome clean, npm ci --dry-run verified against the regenerated lockfile.

@jerannjerann 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.

Round-2 automated pre-review - hub is now covered by the canon (40 routed candidates against this diff, up from independent-probing-only in round 1). Independently re-verified all 4 round-1 findings (the ReDoS url pattern, the compileRegex guard that didn't actually guard, uri accepting trailing garbage, unpinned tsx in CI) as genuinely fixed at this head - not re-flagging any of them. 2 changes needed, 1 suggestion below.

Needs a change

  • The format-vector conformance check (and the comments describing it) overclaim what it catches - it only compares hub's patterns against hub's own local vector copy, never against connect's actual canonical source, so a future upstream addition or a safety-preserving-but-unsafe regex regression would pass silently. This already happened once in this PR's own history: at commit 3224b95, uri was still the loose .+$ form with only 3 local reject vectors (no trailing-garbage cases), and CI stayed green the whole time. scripts/check-format-vectors.ts:11 (commented inline) (test-045a, test-045b, test-045c, proc-073b)
  • The PR description is empty despite the diff changing user-visible behaviour (new client-side validation, a new fail-open path, a new CustomEvent, a new CI gate), so two known gaps - the pre-existing domain-rule interpolation and the vector-check blind spot above - are recorded nowhere a reviewer would see them. PR description (proc-038a, proc-013d)

Suggestions

  • Branch is feat(eng-821)/setup-and-config-field-validation; PR title and 7 of 9 commit subjects say eng-823. Pick one so it doesn't misfile in a ticket search. PR title / branch name (proc-079a)

— eng-review · canon 4b2291af · automated pre-review, not a human review

Comment threadscripts/check-format-vectors.ts
Extends check-format-vectors.ts from vector-only to three layers: pinned
canonical regex sources (catches language-identical rewrites the vectors
cannot see) and a live comparison against connect main's registry (catches
an upstream format added after the snapshot), degrading loudly when the
canonical file is unreachable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chandrajeet-singh

Copy link
Copy Markdown
Author

Addressed the comments and run the eng-review locally -

image

@StuBehanStuBehan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The recorder now genuinely outlives schema rebuilds, and the two regression tests pin the lifetime rather than the comment. isSecretPlaceholder is the right guard and is tested against the real sentinel shape. npm test running in CI closes the last of my July list.

One thing left, on the format gate: layer 3 cannot fire and its message misattributes why. Inline. Not a merge blocker on its own, but I would not want it merged as-is and then trusted.

Comment threadscripts/check-format-vectors.ts Outdated
connect is a private repo, so an unauthenticated CI fetch of its canonical
formatPatterns.ts 404s permanently. Remove the network Layer 3 and its regex
parser, keep the two offline layers (vectors + pinned sources), and rewrite the
headers to state that an upstream format ADD now needs a manual sync. Add
trailing-text reject vectors to email/uuid/date to anchor the patterns.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chandrajeet-singhchandrajeet-singh left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

eng-review (automated pre-review) ran against this PR at 732736e. Two canon items surfaced; both were reviewed and dispositioned as intentional, so there is nothing to change.

Reviewed, accepted as intentional

  • The format-conformance gate (scripts/check-format-vectors.ts) is one-directional: a format ADDED to connect's canonical registry cannot fail hub CI, because both sides of every layer are hub-local snapshots and connect is private (no authenticated live compare from public CI). Accepted for now, mitigated by the documented manual-sync contract in the gate header (bump registry copy + vectors + pinned sources together). (test-045a, test-045b, test-045c)
  • Ticket-ID split: branch feat(eng-821)/... vs title/commits feat(ENG-823). Intentional and already documented in the PR body (branch = parent ticket, commits/title = implementation ticket). (proc-079a)

No blocking findings. The validation core (fail-open regex compile, saved-secret carve-out, ReDoS heuristic) verified clean; biome check clean on all touched files; the new gate is wired into npm test -> node-ci.yml.

-- eng-review · summary · canon b8dcf50c · automated pre-review, not a human review

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@chandrajeet-singh@jerann@StuBehan