Skip to content

feat: Phase 1 foundation — tests, CI, content accuracy, security - #4

Merged
Steel-tech merged 7 commits into
mainfrom
feat/foundation-hardening
Jun 18, 2026
Merged

feat: Phase 1 foundation — tests, CI, content accuracy, security#4
Steel-tech merged 7 commits into
mainfrom
feat/foundation-hardening

Conversation

@Steel-tech

@Steel-tech Steel-tech commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Phase 1 (Foundation) of the IronForge maximization roadmap (docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md). U1 (the model fix) already merged via #1; this PR carries U2–U6 on top, now targeting main directly.

Supersedes #2, which GitHub auto-closed when its base branch fix/ai-model-retired was deleted on #1's merge. Same head branch, same commits (c318a4d), no work lost.

Units

  • U2 — Test infra + CI gate. First tests in the repo: Vitest (+ Testing Library/jsdom) for unit/component, Playwright for e2e, and .github/workflows/ci.yml gating lint + typecheck + test. ESLint was a no-op; now uses eslint-config-next native flat core-web-vitals (21 pre-existing react-hooks violations downgraded to warnings, flagged for burndown).
  • U3 — Estimator hardening. Rule-of-thumb figures extracted to lib/estimator/constants.ts; inputs clamped at the calc boundary (and capped at 1e9 so absurd values can't overflow the bid); bonding markup derived from the profit/contingency constants.
  • U5 — Content accuracy. StateVerification metadata on StateData (WA/OR backfilled); persistent "not legal advice — verify with your state agency" disclaimer on every wizard step, with a last-verified date for verified states and a "not independently verified" notice for the 48 machine-generated ones.
  • U4 — Silent fallback fixed. getPhaseContent no longer serves Washington's content for an unknown state — it returns a labeled "data unavailable" phase. Contract test covers all 50 states × 7 phases.
  • U6 — API security. CSRF/origin gate extended from /api/chat to all API routes; security headers (CSP/HSTS/…) centralized in next.config.ts.

Verification

  • 49 tests across 8 files, all green · tsc --noEmit clean · lint clean · production build succeeds.
  • Code review: 5 reviewers (correctness, security, adversarial, maintainability, testing). No P0/P1. The security change was validated sound (CSRF gate closes a real pre-existing gap on bid-review/onboarding). Applied the high-value findings: estimator overflow cap + the test-coverage gaps (cost-line flags, non-union burden, cost-per-sqft, wizard navigation, unavailable/static phases).

Deferred / surfaced (not in this PR)

  • Durable (cross-instance) rate limiter + x-real-ip-first IP keying → plan U10 (Growth).
  • CSP 'unsafe-inline' on script-src (Next App Router constraint) → future nonce-based CSP.
  • Origin/Host divergence behind a rewriting proxy → env-driven origin allowlist if the deploy topology ever needs it (dormant on Vercel).
  • Pre-existing estimator dead fields (floors, craneType) and the 21 react-hooks lint warnings → depth/polish pass (U11/U12).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added legal disclaimers with state verification status to the wizard workflow.
    • Added state-specific unavailable content messaging for regions with missing data.
  • Tests

    • Introduced comprehensive test coverage for components, content APIs, and calculations.
    • Added end-to-end testing infrastructure.
  • Chores

    • Established automated CI pipeline for code quality checks.
    • Enhanced security headers and CSRF protection.
    • Expanded development tooling and testing frameworks.

Steel-tech and others added 7 commits June 17, 2026 13:42
Set up the project's first test infrastructure: Vitest (jsdom + Testing
Library) for unit/component tests, Playwright for E2E, and a GitHub
Actions workflow gating PRs on lint + typecheck + test.

Replace the no-op ESLint config with eslint-config-next's native flat
core-web-vitals rules (no FlatCompat). Pre-existing react-hooks
violations are downgraded to warnings so the gate is green from day one,
to be burned down in the depth/polish pass. Seed one passing test per
layer (unit, component, e2e); e2e runs locally and is not in the CI gate.

Implements U2 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

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

Move the inline rule-of-thumb figures (burden %, $/ton, profit, bonding,
etc.) into lib/estimator/constants.ts; clamp negative/NaN numeric inputs
at the calculation boundary so a bypassed HTML min hint can't poison a
bid; derive the bonding markup from PROFIT_PCT/CONTINGENCY_PCT so it
can't drift from the profit/contingency constants. Add 13 tests: golden
total, per-ton + compounding-order locks, monopolistic-WC and
prevailing-wage behavior, and clamping edge cases.

Implements U3 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make data provenance a first-class field: StateVerification
(lastVerified / sourceUrl / effectiveDate) on StateData, backfilled for
the hand-crafted WA and OR states. getStateVerification() resolves a
well-formed view for every state — verified states carry their date,
machine-generated states report unverified rather than a misleading one.

A persistent, non-dismissable LegalDisclaimer renders on every wizard
step: "not legal advice — verify with your state agency", plus the
last-verified date for verified states or a "not independently verified"
notice for the machine-generated ones.

Implements U5 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getPhaseContent fell back to Washington's content for any state missing
from the registry — silently presenting one state's regulatory facts as
another's. Replace all six fallbacks with an explicit, labeled
"data unavailable for <state>" phase that points the user at their own
agency. Add a contract test asserting all 50 states x 7 phases resolve to
valid non-empty steps, that a non-WA state gets generated (not
Washington) content, that an unknown code gets the labeled phase (never
Washington), and that the chat-route step lookup finds a phase's steps.

Implements U4 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The proxy enforced same-origin only on /api/chat. Generalize it to every
state-changing API request (non-GET/HEAD on /api/*), so /api/bid-review
and /api/onboarding are no longer open to cross-site calls. Move the full
security-header set (CSP, HSTS, frame/permissions policy) into
next.config.ts as the single source of truth — it covers static assets
the middleware matcher excludes — and drop the duplicate weaker set.
Narrow the proxy matcher to /api/*. Rate limiting stays in the route
handlers (in-memory, per-instance; a durable limiter is plan U10).

Implements U6 of docs/plans/2026-06-17-001-feat-maximize-ironforge-plan.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the foundation code review (no P0/P1; these are the high-value P2/P3s):
- Clamp estimator inputs to MAX_FIELD_VALUE (1e9) so an absurd or tampered
  value (e.g. 1e307) can't overflow the bid to Infinity/NaN — the clamp's
  stated invariant was false for large finite values.
- Add the flagged missing tests: overflow finiteness; estimator cost-line
  flags in the "on" direction, non-union burden, and cost-per-sqft; wizard
  navigation (getNextStep/getPrevStep across phase boundaries); and the
  unavailable / legal-federal / unknown-phase content paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI's node 22 npm rejected the lockfile (Missing @swc/helpers@0.5.23) while
node 24 (local dev + lockfile generator) accepts it via `npm ci`. Align
CI to node 24 so the clean install matches the committed lockfile.

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

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a state verification metadata system that drives a new LegalDisclaimer wizard component, replaces hardcoded WA phase fallbacks with a typed unavailable placeholder, refactors estimator bid math onto extracted constants with input clamping, simplifies the proxy middleware to a CSRF same-origin gate while moving security headers into next.config.ts, and installs Vitest/Playwright test infrastructure with a CI workflow.

Changes

State Verification, Legal Disclaimer, and Phase Content

Layer / File(s) Summary
StateVerification type and registry metadata
lib/types/content.ts, content/state-registry.ts, content/state-registry.test.ts
StateVerification interface added with lastVerified/sourceUrl/effectiveDate; StateData extended with optional verification field; OR and WA registry entries populated; getStateVerification exported function maps any code to a StateVerificationView.
LegalDisclaimer component and step-content wiring
components/wizard/legal-disclaimer.tsx, components/wizard/step-content.tsx, components/wizard/legal-disclaimer.test.tsx
New LegalDisclaimer component calls getStateVerification and conditionally renders verified/unverified disclosure with optional source link; wired into StepContent via profile?.state; four rendering scenarios tested.
Phase unavailablePhase helper and fallback replacement
content/phases.ts, content/phases.test.ts
unavailablePhase(phaseId, state) internal helper builds a structured placeholder Phase for missing registry data; all six phase getters (getBusinessFormation, getContractorLicense, getBonding, getInsurance, getCertifications, getUnionSignatory) updated to return it instead of the WA hardcoded fallback; full phase contract and navigation tested.

Estimator Constants Refactor

Layer / File(s) Summary
Estimator constants extraction
lib/estimator/constants.ts
New file exports PROJECT_TYPE_FACTOR, MONOPOLISTIC_WC_STATES, MAX_FIELD_VALUE clamp bound, and frozen ESTIMATE_CONSTANTS object covering all labor, burden, insurance, materials, equipment, and overhead/profit/contingency parameters.
calculateEstimate refactored onto constants
lib/estimator/calculate.ts, lib/estimator/calculate.test.ts
All hard-coded numeric literals replaced with imported constant references; clampNonNegative helper added; all input fields clamped at calculation entry; bonding/markup multipliers derived from PROFIT_PCT/CONTINGENCY_PCT/BONDING_PCT; 237-line test suite covers happy path, state-driven rate differences, clamping, optional cost lines, and a pinned golden-value regression.

Proxy Middleware and Security Headers

Layer / File(s) Summary
CSRF same-origin gate and security header consolidation
proxy.ts, next.config.ts, proxy.test.ts
proxy.ts reduced to an isSameOrigin helper and a 403-or-pass gate for non-GET/HEAD /api/* requests; rate limiting and header injection removed; config.matcher narrowed to /api/:path*; CSP (with dev unsafe-eval) and HSTS moved to next.config.ts; gate behavior tested across cross-origin, missing, malformed, same-origin, and GET cases.

Test Infrastructure

Layer / File(s) Summary
Test tooling, configs, and CI workflow
package.json, vitest.config.ts, vitest.setup.ts, playwright.config.ts, eslint.config.mjs, .gitignore, .github/workflows/ci.yml
Vitest (jsdom, SWC, jest-dom matchers), Playwright E2E (Chromium, auto-managed dev server), and new typecheck/test/test:e2e scripts added; ESLint upgraded to flat config with eslint-config-next/core-web-vitals; artifact patterns gitignored; CI workflow runs lint, typecheck, and test on PRs and main pushes.
Smoke and utility tests
components/ui/badge.test.tsx, lib/utils.test.ts, lib/ai/models.test.ts, tests/e2e/home.spec.ts
Baseline tests for Badge rendering, cn Tailwind class merging, MODELS registry non-empty strings with retired-model regression guard, and a Playwright E2E smoke test verifying the home page returns status < 400.

Sequence Diagram(s)

sequenceDiagram
  participant StepContent
  participant LegalDisclaimer
  participant getStateVerification
  participant StateRegistry as STATE_REGISTRY

  StepContent->>LegalDisclaimer: render stateCode (from profile?.state)
  LegalDisclaimer->>getStateVerification: getStateVerification(stateCode)
  getStateVerification->>StateRegistry: lookup state entry
  StateRegistry-->>getStateVerification: StateData (with optional verification)
  getStateVerification-->>LegalDisclaimer: StateVerificationView { verified, lastVerified, sourceUrl, stateName }
  alt verified === true
    LegalDisclaimer-->>StepContent: render "Last verified [date]" with source link
  else verified === false
    LegalDisclaimer-->>StepContent: render "Auto-generated – not independently verified"
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 Hop, hop, the constants sing,
No more magic numbers hiding!
WA won't fake its licensing—
Unknown states get their own "unavailing."
The CSRF gate stands firm and true,
Tests bloom like clover, green and new. 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Phase 1 foundation — tests, CI, content accuracy, security' directly summarizes the main changes: it introduces Phase 1 with five major units (U2–U6) covering tests, CI workflow, estimator constants, content verification, and API security—all core components of the changeset.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/foundation-hardening

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
proxy.test.ts (1)

43-57: ⚡ Quick win

Add test coverage for PUT, PATCH, DELETE, and HEAD methods.

The proxy gates all non-GET/HEAD methods, but only POST is tested. Consider adding explicit tests for:

  • PUT/PATCH/DELETE blocked when cross-origin
  • HEAD bypasses the gate (like GET)

This ensures future refactors don't accidentally break handling for these methods.

🧪 Suggested additional test cases
+  it("blocks cross-origin PUT/PATCH/DELETE requests", () => {
+    for (const method of ["PUT", "PATCH", "DELETE"]) {
+      const res = proxy(
+        request("/api/chat", method, { origin: "https://evil.example", host: HOST }),
+      );
+      expect(res.status).toBe(403);
+    }
+  });
+
+  it("does not gate safe HEAD requests", () => {
+    const res = proxy(
+      request("/api/chat", "HEAD", { origin: "https://evil.example", host: HOST }),
+    );
+    expect(res.status).not.toBe(403);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@proxy.test.ts` around lines 43 - 57, The test file currently only covers POST
and GET methods but the proxy gates all non-GET/HEAD methods. Add new test cases
using the same pattern as the existing tests: create separate test cases for
PUT, PATCH, DELETE, and HEAD methods. For PUT, PATCH, and DELETE, verify they
are blocked when cross-origin by checking that res.status equals 403 (opposite
of the current POST test logic). For HEAD, verify it bypasses the gate like GET
does by checking that res.status is not 403. Use the proxy function with the
request helper, passing the appropriate HTTP method and cross-origin headers
(origin: "https://evil.example", host: HOST) to ensure the methods are properly
gated or allowed as expected.
components/wizard/legal-disclaimer.tsx (1)

34-85: ⚡ Quick win

Adopt shadcn/ui base primitives for the disclaimer container.

This new component is implemented with raw HTML nodes only, which drifts from the repository’s required component baseline. Please wrap this in the project’s shadcn/ui primitives (e.g., alert/card composition) and keep the cyberpunk variants on top.

As per coding guidelines, "Use shadcn/ui components as the base component library and extend with custom cyberpunk variants (e.g., .btn-neon-solid, .btn-neon-cyan, .cyber-card) in globals.css".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/wizard/legal-disclaimer.tsx` around lines 34 - 85, Replace the raw
HTML div container (the one with role="note" and aria-label="Legal disclaimer")
with a shadcn/ui primitive component such as Alert or Card that provides
semantic structure and accessibility out of the box. Maintain the cyberpunk
visual styling by applying the custom className variants (neon-amber, neon-blue
colors and border/background patterns) on top of the shadcn/ui component. Update
all nested div and p elements to use appropriate shadcn/ui sub-components or
typography primitives where available, ensuring the component follows the
repository's pattern of using shadcn/ui as the base component library with
cyberpunk customizations applied via globals.css variants.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 12-15: The GitHub Actions workflow uses mutable version tags (`@v4`)
for both actions/checkout and actions/setup-node actions instead of pinning to
specific commit SHAs, which weakens supply-chain security. Additionally, the
actions/checkout action retains default credential persistence which increases
token exposure risk. Replace the `@v4` mutable tags with specific commit SHAs for
both the actions/checkout and actions/setup-node actions, and add a token
persistence configuration to the actions/checkout action to explicitly disable
credential persistence after checkout completes.

In `@lib/estimator/constants.ts`:
- Around line 22-24: The MONOPOLISTIC_WC_STATES constant duplicates regulatory
classification data that already exists in the STATE_REGISTRY from
content/state-registry.ts using the wcType property. Remove the hard-coded
MONOPOLISTIC_WC_STATES set and instead derive the monopolistic states
dynamically by filtering the STATE_REGISTRY for states where the wcType field
matches the monopolistic regime classification. Update any references to
MONOPOLISTIC_WC_STATES in lib/estimator/calculate.ts and elsewhere to use this
derived filter from STATE_REGISTRY instead, ensuring the single source of truth
pattern is maintained.

---

Nitpick comments:
In `@components/wizard/legal-disclaimer.tsx`:
- Around line 34-85: Replace the raw HTML div container (the one with
role="note" and aria-label="Legal disclaimer") with a shadcn/ui primitive
component such as Alert or Card that provides semantic structure and
accessibility out of the box. Maintain the cyberpunk visual styling by applying
the custom className variants (neon-amber, neon-blue colors and
border/background patterns) on top of the shadcn/ui component. Update all nested
div and p elements to use appropriate shadcn/ui sub-components or typography
primitives where available, ensuring the component follows the repository's
pattern of using shadcn/ui as the base component library with cyberpunk
customizations applied via globals.css variants.

In `@proxy.test.ts`:
- Around line 43-57: The test file currently only covers POST and GET methods
but the proxy gates all non-GET/HEAD methods. Add new test cases using the same
pattern as the existing tests: create separate test cases for PUT, PATCH,
DELETE, and HEAD methods. For PUT, PATCH, and DELETE, verify they are blocked
when cross-origin by checking that res.status equals 403 (opposite of the
current POST test logic). For HEAD, verify it bypasses the gate like GET does by
checking that res.status is not 403. Use the proxy function with the request
helper, passing the appropriate HTTP method and cross-origin headers (origin:
"https://evil.example", host: HOST) to ensure the methods are properly gated or
allowed as expected.
🪄 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: 338bcdb6-fe2a-4cd7-8b53-357cd096fb8d

📥 Commits

Reviewing files that changed from the base of the PR and between 8297806 and c318a4d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (25)
  • .github/workflows/ci.yml
  • .gitignore
  • components/ui/badge.test.tsx
  • components/wizard/legal-disclaimer.test.tsx
  • components/wizard/legal-disclaimer.tsx
  • components/wizard/step-content.tsx
  • content/phases.test.ts
  • content/phases.ts
  • content/state-registry.test.ts
  • content/state-registry.ts
  • eslint.config.mjs
  • lib/ai/models.test.ts
  • lib/estimator/calculate.test.ts
  • lib/estimator/calculate.ts
  • lib/estimator/constants.ts
  • lib/types/content.ts
  • lib/utils.test.ts
  • next.config.ts
  • package.json
  • playwright.config.ts
  • proxy.test.ts
  • proxy.ts
  • tests/e2e/home.spec.ts
  • vitest.config.ts
  • vitest.setup.ts

Comment thread .github/workflows/ci.yml
Comment on lines +12 to +15
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
# Match the local dev / lockfile npm version — node 22's older npm

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 | ⚡ Quick win

Harden workflow actions and disable credential persistence

Line 12 and Line 13 use mutable action tags (@v4) instead of commit SHAs, and Line 12 keeps default credential persistence. This weakens CI supply-chain guarantees and increases token exposure risk.

Suggested patch
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<FULL_COMMIT_SHA>
+        with:
+          persist-credentials: false
-      - uses: actions/setup-node@v4
+      - uses: actions/setup-node@<FULL_COMMIT_SHA>
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 12-12: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 12-12: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 13-13: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 12 - 15, The GitHub Actions workflow
uses mutable version tags (`@v4`) for both actions/checkout and actions/setup-node
actions instead of pinning to specific commit SHAs, which weakens supply-chain
security. Additionally, the actions/checkout action retains default credential
persistence which increases token exposure risk. Replace the `@v4` mutable tags
with specific commit SHAs for both the actions/checkout and actions/setup-node
actions, and add a token persistence configuration to the actions/checkout
action to explicitly disable credential persistence after checkout completes.

Source: Linters/SAST tools

Comment on lines +22 to +24
/** Monopolistic workers'-comp states (state-run fund, higher WC burden). */
export const MONOPOLISTIC_WC_STATES = new Set(["WA", "OH", "ND", "WY"]);

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 | ⚡ Quick win

Use STATE_REGISTRY as the single source of truth for WC regime detection.

This hard-coded state set duplicates regulatory classification data already modeled in content/state-registry.ts (wcType), so future registry updates can silently desync burden/insurance math in lib/estimator/calculate.ts.

Suggested direction
- export const MONOPOLISTIC_WC_STATES = new Set(["WA", "OH", "ND", "WY"]);
+ // Remove duplicated classification set.
+ // In calculate.ts, derive from registry metadata:
+ // const isMonopolistic = STATE_REGISTRY[stateCode]?.wcType === "monopolistic";

As per coding guidelines, content/state-registry.ts should be used as the “central data source for all 50 states regulatory information”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/estimator/constants.ts` around lines 22 - 24, The MONOPOLISTIC_WC_STATES
constant duplicates regulatory classification data that already exists in the
STATE_REGISTRY from content/state-registry.ts using the wcType property. Remove
the hard-coded MONOPOLISTIC_WC_STATES set and instead derive the monopolistic
states dynamically by filtering the STATE_REGISTRY for states where the wcType
field matches the monopolistic regime classification. Update any references to
MONOPOLISTIC_WC_STATES in lib/estimator/calculate.ts and elsewhere to use this
derived filter from STATE_REGISTRY instead, ensuring the single source of truth
pattern is maintained.

Source: Coding guidelines

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c318a4d134

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread proxy.ts
// === Security headers on all responses ===
const response = NextResponse.next();
return addSecurityHeaders(response);
return NextResponse.next();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore trusted-IP rate limiting for chat

When /api/chat is deployed behind a proxy that preserves client-supplied X-Forwarded-For, this now just passes the request through after the CSRF check. The only remaining chat limiter keys on x-forwarded-for before x-real-ip (app/api/chat/route.ts:27-31), so an abusive client can rotate that header and avoid the 20/min cap that the removed proxy limiter previously enforced using x-real-ip.

Useful? React with 👍 / 👎.

@Steel-tech
Steel-tech merged commit 638c12f into main Jun 18, 2026
3 of 4 checks passed
@Steel-tech
Steel-tech deleted the feat/foundation-hardening branch June 18, 2026 06:26
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