Skip to content

DC-172: Enrollment checker API endpoint with plugin integration#64

Closed
jamesmblair wants to merge 54 commits intomainfrom
feature/DC-172-enrollment-checker
Closed

DC-172: Enrollment checker API endpoint with plugin integration#64
jamesmblair wants to merge 54 commits intomainfrom
feature/DC-172-enrollment-checker

Conversation

@jamesmblair
Copy link
Copy Markdown
Member

Summary

  • POST /api/enrollment/check — public, unauthenticated endpoint for checking children's Summer EBT enrollment status
  • Delegates state-specific matching to the plugin system via IEnrollmentCheckService
  • Server-generated correlation IDs (CheckId) per child
  • De-identified persistence for analytics (birth year only, SHA-256 hashed IPs, no PII)
  • Per-IP fixed window rate limiting with policy-aware OnRejected handler
  • PortalWebApplicationFactory for integration testing infrastructure
  • 13 new tests (8 unit + 5 integration), 437 total all passing

Key files

Layer Files
API EnrollmentCheckController, API request/response models, rate limit settings
Use Cases CheckEnrollmentCommand, CheckEnrollmentCommandHandler
Core Domain models, IEnrollmentCheckSubmissionLogger
Infrastructure EF entities, migration, EnrollmentCheckSubmissionLogger, DesignTimePortalDbContextFactory
Plugin IEnrollmentCheckService registration in ServiceCollectionPluginExtensions

Dependencies

  • Requires state-connector PR to merge first
  • DC and CO connector PRs provide the plugin implementations

🤖 Generated with Claude Code

James Blair and others added 30 commits March 3, 2026 11:56
Design documents for the multi-state enrollment checking capability
that allows parents/guardians to check Summer EBT enrollment status
without logging in.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion

Add POST /api/enrollment/check — a public, unauthenticated endpoint
that allows parents/guardians to check children's Summer EBT enrollment
status. Delegates state-specific matching to the plugin system via
IEnrollmentCheckService.

Key changes:
- Register IEnrollmentCheckService in MEF plugin conventions
- CheckEnrollmentCommandHandler with server-generated correlation IDs
- De-identified persistence (birth year only, SHA-256 hashed IPs, no PII)
- Per-IP fixed window rate limiting (enrollment-check-policy)
- Policy-aware OnRejected handler for correct 429 error messages
- EF Core migration for EnrollmentCheckSubmissions table
- DesignTimePortalDbContextFactory for EF tooling support
- Public partial Program class for WebApplicationFactory support
- 8 unit tests + 5 integration tests (437 total, all passing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hard-coded "otp-policy" and "enrollment-check-policy" strings
with constants to prevent silent mismatches across controllers and
Program.cs configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e fallback

Add integration tests that load real MEF plugins (DC, CO) and exercise
the full HTTP pipeline through to actual backends. Tests skip gracefully
when plugin DLLs or credentials are unavailable. Also add a default
IEnrollmentCheckService that returns NonMatch when no plugin is loaded,
and harden PortalWebApplicationFactory's env var handling to prevent
race conditions between test collections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…line

Documents the approach for eliminating env var usage in test factories
by changing AddPlugins from an IServiceCollection extension to an
IHostBuilder extension. This defers plugin loading to the Build()
pipeline where WAF's ConfigureAppConfiguration has already run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eCollection

Move plugin loading from inline Program.cs execution to a
ConfigureServices callback. This runs during Build(), after WAF's
ConfigureAppConfiguration, enabling tests to override plugin config
via standard .NET patterns instead of environment variables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ugin loading

Update the design doc with corrected approach after discovering that
ConfigureHostBuilder.ConfigureServices runs immediately in minimal
hosting. Add implementation plan with 6 tasks for the factory delegate
approach.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract MEF loading logic into a singleton that loads lazily on first
DI resolution. Takes IConfiguration via constructor injection, so it
reads the fully-assembled config including WAF test overrides.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ates

Replace eager MEF loading with factory delegates that defer to
PluginLoader. AddPlugins no longer takes IConfiguration — config is
read at resolution time when it's fully assembled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…guration

Replace env var constructor/dispose lifecycle with standard
ConfigureAppConfiguration and in-memory collection. No more
process-global state mutation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ureAppConfiguration

Replace env var constructor/dispose lifecycle with standard
ConfigureAppConfiguration. Config overrides are now scoped to each
factory instance instead of mutating process-global state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests no longer mutate process-global state, so [Collection] serialization
is unnecessary. Update config override keys from env var format (__) to
standard .NET hierarchical format (:). Delete PluginIntegrationCollection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ntials

Replace Environment.GetEnvironmentVariable() with ConfigurationBuilder
that chains env vars and user secrets, providing a standard secure way
to store CBMS API credentials locally while preserving CI compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ment-checker

# Conflicts:
#	src/SEBT.Portal.Api/Composition/ServiceCollectionPluginExtensions.cs
Restore the deferred PluginLoader pattern so MEF plugin assemblies load
lazily at DI resolution time, when IConfiguration is fully assembled
(including WebApplicationFactory test overrides). This fixes plugin
integration tests that were broken by eager config reads at startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Documents the architecture for the new SEBT.EnrollmentChecker.Web app:
shared design-system package, SSR/SSG deployment modes, form state model,
content pipeline, component structure, and two-workstream implementation plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add packages/design-system as a pnpm workspace package (@sebt/design-system).
Update .gitignore to allow the top-level packages/ directory, which was previously
excluded by the NuGet package-restore ignore rule (**/[Pp]ackages/*).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… paths

Copies USWDS bundle, theme, and component SASS files from
src/SEBT.Portal.Web/design/sass/ to packages/design-system/design/sass/,
updates next.config.ts to resolve SASS includePaths via the @sebt/design-system
workspace dependency, and updates styles.scss to forward 'uswds-bundle'
without a relative path prefix.

Also adds @sebt/design-system as a workspace dependency to the portal's
package.json so pnpm creates the required node_modules symlink.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ign-system

Copies UI components (Button, InputField, Alert, TextLink), layout components
(Header, Footer, HelpSection, SkipNav, LanguageSelector), the I18nProvider, and
lib files (i18n, state, links) into packages/design-system/src/. Refactors i18n
initialization into an exported initI18n() function so apps supply their own
generated locale resources. Adds vitest config and barrel index.ts to the package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update all portal source files to import components (Alert, Button, InputField,
TextLink, layout), providers (I18nProvider), and lib utilities (state, links,
i18n) from @sebt/design-system instead of local @/components/ and @/lib/ paths.
Delete the now-redundant local source files. Configure next.config.ts with
transpilePackages, serverExternalPackages, and webpack aliases to ensure a single
React instance and prevent react-i18next from being evaluated in the RSC context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oviders, and USWDS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- childSchema and enrollmentSchema Zod schemas with request/response types
- EnrollmentContext with sessionStorage persistence and CRUD actions
- checkEnrollment and getSchools API functions with error handling
- useSchools TanStack Query hook
- MSW handlers for /api/enrollment/check and /api/enrollment/schools
- Fix vitest.config.ts alias ordering so @/content resolves before @

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
James Blair and others added 24 commits March 12, 2026 21:47
…d, result display)

Implements LandingPage, DisclaimerPage, ClosedPage, ChildReviewCard, ChildResultCard,
EnrolledSection, and NotEnrolledSection with full TDD coverage. Also adds required
i18n keys (heading, cta, editChild, removeChild, enrolledHeading, etc.) to all
state/locale content files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hildFormPage, ReviewPage, ResultsPage)

Implements Task 4 of the enrollment checker: school selection dropdown, child
info form with Zod validation, add/edit child page, review list page, and
results display page. Adds required i18next content keys to all locale files
(en/es × co/dc) for personalInfo, confirmInfo, result, and common namespaces.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…missing keys

Update enrollment checker components to reference the key names that the
generate-locales script produces from the state CSVs, rather than the
custom names the implementer invented. Fixes 6 mismatches:
- landing/disclaimer/personalInfo/confirmInfo: t('heading') → t('title')
- personalInfo: t('dobLabel') → t('labelBirthdate')
- confirmInfo: t('addAnotherChild') → t('actionAdd')

Update ChildForm tests to match the new label text ('Birthdate' not
'Date of birth').

Add docs/content/enrollment-checker-missing-csv-keys.md listing the 20
content keys that still need to be added to co.csv and dc.csv before the
manually-written locale JSON files can be removed from version control.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copies CDHS logo, state seal, and translate icon from portal
for both CO and DC. Fixes broken images in header and footer.
Fixes i18n namespace collision where portal S3 rows overwrote
enrollment checker S1 values for personalInfo and confirmInfo.

ADR-0009 documents the decision.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renders markdown-formatted locale strings as React elements.
Supports bold, paragraph breaks, and inline mode.

ADR-0010 documents the decision.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ChildFormValues now has separate month, day, year fields for the
USWDS memorable-date form pattern. Child type stores composed
dateOfBirth as ISO string. Conversion helpers bridge the two.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces single Continue button with Apply now / Aplica ahora.
Adds expandable FAQ section with eligibility details.
Body text now renders bold markdown via RichText.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses USWDS memorable-date pattern with month dropdown to
prevent MM/DD transposition. Adds name field hints and
switches cancel button to Back label.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rewrites ChildReviewCard to add Name/Birthdate labels, format dates as
"Month, Day Year", include middle initial, and replace Edit/Remove
buttons with a single "Update this child's information" link (removing
the onRemove prop per Figma design). Rewrites ReviewPage to add a
description paragraph, horizontal rules between child cards, Back+Submit
button group, and "Add another child" as a text link below. Updates
tests to match the new component interfaces.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updates test files that reference old childSchema, dateOfBirth
field, and button labels to match the new month/day/year schema
and structural component changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- enrollment-checker-figma-comparison.md: visual fidelity gap analysis
- design spec: 5 workstreams for closing those gaps
- implementation plan: 11 tasks across 4 chunks
- regenerated locale resources after section filtering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The barrel export from @sebt/design-system re-exports react-i18next-dependent
modules (I18nProvider, initI18n). When layout.tsx (a Server Component) imported
from the barrel, react-i18next was pulled into the RSC bundle causing a
createContext error. The previous workaround (serverExternalPackages) fixed that
but broke client component SSR with a useMemo null dispatcher error.

Fix: use direct subpath imports in layout.tsx and Providers.tsx to keep
react-i18next out of the RSC module graph entirely, matching the portal's
pattern on main. Remove the now-unnecessary serverExternalPackages config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Figma verification found two gaps: the review page showed a raw
"addAnotherChild" key (missing from all locale files) and the child
form's middle name field lacked the "Optional" hint text shown in
the design.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nAdd key

Locale JSON files are generated from CSVs — never hand-edit them. The
addAnotherChild key we added manually already exists as actionAdd in the
confirmInfo namespace (generated from the spreadsheet). Updated ReviewPage
to use t('actionAdd') from confirmInfo instead of tCommon('addAnotherChild').

Added a hard rule to CLAUDE.md documenting the locale generation workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the "SUMMER EBT" logo above the landing page heading and the
character/mascot icon above the child form heading, matching the
Figma design frames. Assets exported from Figma MCP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add review page clipboard icon (icon-review-card.svg) to match Figma
- Fix button layout on ChildForm and ReviewPage to be side-by-side
  instead of full-width stacked (use USWDS flex utilities)
- Create figma-verify skill for repeatable design verification
- Document CO landing page content mismatch (spreadsheet fix needed)
- Gitignore .claude-figma-verify/ and screenshots/ directories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…spacing

Set --font-urbanist CSS variable to Public Sans for the enrollment checker
and add USWDS font-family-sans class to all headings for consistent
typography without !important overrides. Shrink the memorable-date month
select at mobile widths so Month/Day/Year fit on one row. Replace the
usa-card wrapper on review cards with a faint outline border and heavy
dividers between children to match the Figma design. Reduce icon-to-heading
spacing on form and review pages. Update figma-verify skill with two-pass
screenshot approach using Figma prototype URLs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…uild artifacts

Matches Portal.Web gitignore rules (minus design token/SCSS rules). Removes
tsconfig.tsbuildinfo from tracking and ignores .next/, next-env.d.ts, and
other generated files. Includes Next.js auto-formatted tsconfig.json changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove from git tracking:
- .idea/ (15 JetBrains IDE config files) — developer-specific, not project config
- src/SEBT.EnrollmentChecker.Web/content/locales/ (28 generated locale JSONs)
- src/SEBT.EnrollmentChecker.Web/src/lib/generated-locale-resources.ts
- src/SEBT.EnrollmentChecker.Web/public/{css,fonts,img,js}/ (2601 USWDS assets)

These were committed before their .gitignore rules took effect. The EC Web
.gitignore already excludes them; the root .gitignore now uses a blanket
.idea/ rule instead of a single-file exclusion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jamesmblair jamesmblair closed this Apr 1, 2026
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