Skip to content

refactor(SDK-1127): extract sub flows from PaymentFlow#2445

Closed
mariechatfield wants to merge 8 commits into
mainfrom
refactor/marie/SDK-1127-payment-flow
Closed

refactor(SDK-1127): extract sub flows from PaymentFlow#2445
mariechatfield wants to merge 8 commits into
mainfrom
refactor/marie/SDK-1127-payment-flow

Conversation

@mariechatfield

@mariechatfield mariechatfield commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Carves the create-payment and view-history steps out of PaymentFlow into their own hub+spoke flows, mirroring the PayrollFlow/PayrollExecutionFlow pattern: each spoke owns its own robot3 machine and breadcrumb trail, prefixed with the hub's landing breadcrumb, and bubbles terminal events (exit, cancel, landing navigation) back up to the hub. Pure refactor — no behavior change, all six original events unchanged.
  • Marks the two new standalone flows @alpha (not yet promoted to the public surface) — PaymentFlow still composes them internally, but its @components list and GUIDE.md step-flow diagram point at the underlying public blocks (CreatePayment, PaymentSummary, PaymentHistory, PaymentStatement) they wrap, matching what a partner using the public surface actually sees.

http://localhost:5200/contractormanagement/PaymentFlow behaves the same locally as it does on main
Screenshot 2026-07-22 at 12 32 02 PM

http://localhost:5200/contractormanagement/CreatePaymentFlow works to create a payment (same as the PaymentFlow starting from the New payment button)
Screenshot 2026-07-22 at 12 31 56 PM

http://localhost:5200/contractormanagement/ViewHistoryView lets you view payment details (same as the PaymentFlow starting with the eye icon on an existing payment)
Screenshot 2026-07-22 at 12 32 06 PM

Test plan

  • paymentStateMachine.test.ts characterization test for the prior flat machine, plus new createPaymentMachine.test.ts / viewHistoryMachine.test.ts covering each extracted spoke
  • npm run derive regenerates cleanly with 0 errors; CreatePaymentFlow/ViewHistoryFlow correctly excluded from the generated reference while @alpha
  • Pre-existing, unrelated failure in docs-site/plugins/typedoc-custom/sdk-router.test.ts reproduces on main too — not introduced by this branch

🤖 Generated with Claude Code

@mariechatfield
mariechatfield changed the base branch from main to fix/docs-alpha-exclusion-and-flow-endpoints July 22, 2026 01:08
@mariechatfield mariechatfield changed the title refactor(PaymentFlow): extract CreatePaymentFlow and ViewHistoryFlow spokes (SDK-1127) refactor(SDK-1127): extract sub flows from PaymentFlow Jul 22, 2026
@mariechatfield
mariechatfield force-pushed the refactor/marie/SDK-1127-payment-flow branch from 2e4abc7 to 384f1af Compare July 22, 2026 01:29
Base automatically changed from fix/docs-alpha-exclusion-and-flow-endpoints to main July 22, 2026 18:27
mariechatfield and others added 4 commits July 22, 2026 11:30
…spokes (SDK-1127)

Carves the create-payment and view-history steps out of PaymentFlow into
their own hub+spoke flows, mirroring the PayrollFlow/PayrollExecutionFlow
pattern: each spoke owns its own robot3 machine and breadcrumb trail,
prefixed with the hub's landing breadcrumb, and bubbles terminal events
(exit, cancel, landing navigation) back up to the hub. Prepares the fourth
spoke slot for the upcoming HistoricalPaymentFlow. Pure refactor — no
behavior change, all six original events unchanged.

Adds paymentStateMachine.test.ts as a characterization test for the prior
flat machine, then splits machine-level unit test coverage across the hub
and each new spoke (createPaymentMachine.test.ts, viewHistoryMachine.test.ts).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…alpha

SDK-1127

Not yet promoted to public: PaymentFlow keeps composing them internally,
but its @components list and GUIDE.md step-flow diagram point at the
underlying public blocks (CreatePayment, PaymentSummary, PaymentHistory,
PaymentStatement) they wrap, matching what a partner using the public
surface actually sees.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…agrams

Per .claude/doc-guides/flows.md diagram guidance:

- CreatePaymentFlow is a guided flow (linear, real exit) — switch its
  diagram from LR to TD.
- ViewHistoryFlow: contractor/payments/cancel doesn't exit the flow —
  traced to PaymentHistory.handleCancelPayment, which just refetches and
  stays on PaymentHistory. Drop the done node/edge; this flow has no exit.
  Split the PaymentHistory<->PaymentStatement edge into its two real,
  distinct events (view/details forward, breadcrumb/navigate back)
  instead of one bidirectional edge showing only the forward label.
  Corrected prose that mischaracterized cancel as exiting "the group
  outright."

These flows stay @Alpha and excluded from the generated reference, so
there's no docs/reference diff to regenerate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mariechatfield
mariechatfield force-pushed the refactor/marie/SDK-1127-payment-flow branch from 384f1af to b7c2120 Compare July 22, 2026 18:33
github-actions Bot and others added 4 commits July 22, 2026 18:36
ViewHistoryFlow/PaymentHistory require paymentId, but it wasn't
recognized as an entity kind, so it had no sidebar combobox, no
auto-fetch, and no manual-mode field -- there was no way to supply it
short of hand-editing localStorage. Classify paymentId as an entity ID
(sdk-app/scripts/analyze-component-props.ts), wire it through the
Settings sidebar, auto/manual entity plumbing, and demo provisioning,
same as employeeId/contractorId/payrollId.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Demo companies were provisioned with employees/contractors/payrolls
but never a contractor payment, so ViewHistoryFlow had nothing to
show until you manually created one via PaymentFlow first. When a
freshly provisioned demo has a contractor but no paymentId,
createDemoAndProvision now records a past-dated "Historical Payment"
for it -- record-only, no funding/ACH lead time, so it posts
synchronously. Best-effort: a seed failure doesn't fail demo creation.

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

@boostsecurity-io-ai boostsecurity-io-ai Bot 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.

⚠️  2 New Security Findings

The latest commit contains 2 new security findings.

Findings Note: 2 findings are displayed as inline comments.

Not a finding? Ignore it by adding a comment on the line with just the word noboost.

Scanner: boostsecurity - Semgrep

companyId: string,
contractorId: string,
): Promise<string> {
const contractorRes = await fetch(`${proxyBase}/v1/contractors/${contractorId}`, {

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.

CWE-918: Server-Side Request Forgery (SSRF)
Original Rule ID: rules_lgpl_javascript_ssrf_rule-node-ssrf

Details

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

This rule detected user-controlled URLs being passed to Node.js HTTP client
libraries including axios.get(), axios.post(), fetch(), http.get(),
http.request(), needle(), request(), urllib.request(),
superagent.get(), bent(), got.get(), net.connect(), and
socket.io-client.io(). When user input controls the destination URL of HTTP
requests without validation, Server-Side Request Forgery (SSRF) vulnerabilities
arise. SSRF allows attackers to force the server to make requests to internal
systems, cloud metadata endpoints (such as 169.254.169.254), or other
unauthorized destinations. This can expose internal APIs, databases,
administrative panels, or enable network scanning and pivoting attacks that
bypass firewall rules and network segmentation.
 📘 Learn More

AI Remediation

The fix adds an origin-allowlist re-validation (validateHost) at the start of seedHistoricalContractorPayment before any fetch calls, ensuring the request destination is confined to the pre-approved gws-flows origins and cannot be redirected toward internal systems or cloud metadata endpoints. This closes the SSRF (CWE-918) gap where proxyBase was consumed as an HTTP client URL without validation at the request site.

Suggested change
const contractorRes = await fetch(`${proxyBase}/v1/contractors/${contractorId}`, {
// Re-validate the origin of the proxy base against the allowlist before
// issuing requests to guard against SSRF (CWE-918). proxyBase is derived
// from a validated host plus a flow token, but validating here ensures the
// request destination cannot be redirected to an internal/metadata endpoint.
validateHost(proxyBase)

checkDate.setDate(checkDate.getDate() - 14)

const paymentRes = await fetch(
`${proxyBase}/v1/companies/${companyId}/contractor_payment_groups`,

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.

CWE-918: Server-Side Request Forgery (SSRF)
Original Rule ID: rules_lgpl_javascript_ssrf_rule-node-ssrf

Details

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

This rule detected user-controlled URLs being passed to Node.js HTTP client
libraries including axios.get(), axios.post(), fetch(), http.get(),
http.request(), needle(), request(), urllib.request(),
superagent.get(), bent(), got.get(), net.connect(), and
socket.io-client.io(). When user input controls the destination URL of HTTP
requests without validation, Server-Side Request Forgery (SSRF) vulnerabilities
arise. SSRF allows attackers to force the server to make requests to internal
systems, cloud metadata endpoints (such as 169.254.169.254), or other
unauthorized destinations. This can expose internal APIs, databases,
administrative panels, or enable network scanning and pivoting attacks that
bypass firewall rules and network segmentation.
 📘 Learn More

AI Remediation

The base host of each fetch URL (proxyBase) already flows from the allowlist-validated safeHost, but the interpolated companyId/contractorId path segments were unvalidated, allowing potential URL/path manipulation that could redirect requests to unintended destinations (SSRF). The fix enforces a strict identifier allowlist (^[a-zA-Z0-9_-]+$) and applies encodeURIComponent so these values cannot alter the request target. This constrains the outbound request destination to the validated proxy base with well-formed path segments.

At line 69, do the following changes:

   companyId: string,
   contractorId: string,
 ): Promise<string> {
-  const contractorRes = await fetch(`${proxyBase}/v1/contractors/${contractorId}`, {
-    signal: AbortSignal.timeout(10000),
-  })
+  // Guard against path/URL injection: IDs are interpolated into request URLs,
+  // so restrict them to opaque identifier characters before use.
+  const ID_PATTERN = /^[a-zA-Z0-9_-]+$/
+  if (!ID_PATTERN.test(companyId) || !ID_PATTERN.test(contractorId)) {
+    throw new Error('Invalid company or contractor ID')
+  }
+
+  const contractorRes = await fetch(
+    `${proxyBase}/v1/contractors/${encodeURIComponent(contractorId)}`,
+    {
+      signal: AbortSignal.timeout(10000),
+    },
+  )
   if (!contractorRes.ok) return ''
   const contractor = (await contractorRes.json()) as { wage_type?: string }
 

At line 79, do the following changes:

   checkDate.setDate(checkDate.getDate() - 14)
 
   const paymentRes = await fetch(
-    `${proxyBase}/v1/companies/${companyId}/contractor_payment_groups`,
+    `${proxyBase}/v1/companies/${encodeURIComponent(companyId)}/contractor_payment_groups`,
     {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },

@mariechatfield

Copy link
Copy Markdown
Contributor Author

closed in favor of #2463 once I realized that this introduced new bugs due to missing tests

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant