Skip to content

feat(compliance): provider headquarters filter - #3018

Merged
steebchen merged 14 commits into
mainfrom
feat/provider-headquarters-compliance
Jul 13, 2026
Merged

steebchen merged 14 commits into
mainfrom
feat/provider-headquarters-compliance

Conversation

@steebchen

@steebchen steebchen commented Jul 12, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds a provider headquarters / country filter to the enterprise compliance policy and makes the gateway respect it per organization. Also adds compliance + country filtering to the public providers directory, generates per-country provider pages, distinguishes SOC 2 Type 1 vs Type 2 in the policy (with a data migration for existing policies), and tightens documentation so catalogue-derived lists aren't hardcoded.

Screenshots (verified running locally)

Compliance settings — Provider Headquarters selector. United States + France selected; the Provider Impact panel updates live to 24 of 39 providers, correctly blocking China/Netherlands/Japan providers (DeepSeek, Alibaba, ByteDance, MiniMax, Nebius, Sakana, …) while keeping Mistral (France) allowed.

Compliance settings provider headquarters selector

Public providers directory — compliance + headquarters filters. Filtering by Headquarters = France, GDPR, and No-training narrows the grid to just Mistral AI (FR).

Providers directory filter

Per-country provider page (/providers/country/us).

Per-country provider page

Screenshots were captured driving the real app (seeded DB, enterprise@example.com) and hosted on the assets/pr-3018-proof branch — safe to delete after review; not part of this PR's diff. Taken before the SOC 2 Type 2 toggle was added.

Compliance policy + gateway enforcement

  • packages/models/src/providers.ts: add optional allowedCountries?: string[] to ProviderCompliancePolicy; extend isProviderCompliant() to enforce it. Fail-closed — when the list is non-empty, providers whose headquarters isn't in it (including unknown/null HQ) are blocked. Composes with the existing certification/data-policy requirements.
  • New helpers getProviderCountries() (authoritative closed set derived from the catalogue) and countryCodeToFlag().
  • Gateway: no route changes needed — every provider-selecting path already funnels through isProviderCompliant (chat/videos filter candidates; embeddings/speech/moderations/ocr call assertProviderCompliant; images/anthropic/responses/mcp forward to /v1/chat/completions). Added gateway integration tests (api.spec.ts) proving the country filter blocks (403) and allows (200) on the real request path.

SOC 2 Type 1 vs Type 2 (with migration)

Previously the single requireSoc2 flag accepted any SOC 2 report (Type 1 or 2), yet the dashboard labelled it "SOC 2 (Type 2)". This PR resolves the mismatch by honoring what admins saw:

  • New requireSoc2Type2 requirement — strict dataPolicy.soc2 === 2 — with its own toggle. requireSoc2 remains available as "SOC 2 (Type 1 or 2)" for the genuinely-any case.
  • Data migration (packages/db/migrations/…_migrate_require_soc2_to_type2.sql): converts every stored requireSoc2: true into requireSoc2Type2: true (dropping the old key), so existing policies now mean what their old "Type 2" label promised. false/absent/NULL policies are untouched. Validated against Postgres.
  • requireSoc2OrIso27001 now requires SOC 2 Type 2 or ISO 27001 (previously any SOC 2 or ISO 27001).

Backward-compat answer: an existing policy from before equals requireSoc2: true; after migration it becomes requireSoc2Type2: true (Type 2 required), matching the intent the old label conveyed. Policies that didn't set SOC 2 are unaffected.

API

  • apps/api/src/routes/organization.ts: allowedCountries and requireSoc2Type2 added to the providerCompliancePolicy Zod schema; allowedCountries is refined to reject any code not present in the catalogue.

Compliance settings UI

  • compliance-client.tsx: a flex-wrapped flag-chip country selector, limited to catalogue countries, disabled unless the policy is enabled (empty selection = no restriction). The "Provider Impact" preview reflects the country filter and the SOC 2 toggles live.

Public providers directory

  • Filters on /providers: a Headquarters country dropdown (flag + name) and Compliance requirement toggle chips, reusing isProviderCompliant — the same rules the gateway enforces — composing with search/sort, with a Clear button.
  • New /providers/country/[country] pages (static params + SEO metadata + JSON-LD), reusing ProvidersGrid (now filterable by country). Registered in sitemap.ts; each provider card's HQ badge links to its country page.

Documentation

  • New "Provider headquarters" section in features/compliance.mdx, updated requirements table (SOC 2 Type 1-or-2, SOC 2 Type 2, SOC 2 Type 2 or ISO 27001), and a new changelog entry.
  • Removed hardcoded country lists from docs and changelog — they link to the live providers directory instead.
  • Added an AGENTS.md guideline: never hardcode catalogue-derived lists into docs/marketing; link to the generated page. Exception: image/video generation models.

Notes

  • Catalogue headquarters countries today: US, CN, FR, NL, JP — the selector and country pages are derived from this set. A unit test asserts every catalogue headquarters code has a display-name + valid flag mapping, so future additions can't ship without them.

Testing

  • packages/models/src/compliance.spec.ts: 26 tests — country filter (fail-closed, empty list, composition with certs), SOC 2 Type 1/2 distinction (incl. real Type 1 provider canopywave), the new requireSoc2OrIso27001 Type-2 semantics, getProviderCountries()/countryCodeToFlag(), and mapping-completeness guards.
  • apps/gateway/src/api.spec.ts: gateway integration tests proving country enforcement (403/200) on the real /v1/chat/completions path.
  • Data migration validated against Postgres (only requireSoc2:true rows converted; others untouched).
  • turbo run build --filter=api --filter=ui — green. pnpm format — green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KUzb6QZFn2ngP6eohSDPdU

Add an optional allowedCountries list to the provider compliance
policy so enterprise orgs can restrict routing to providers
headquartered in approved countries. The gateway enforces it through
the existing isProviderCompliant chokepoint (fail-closed for unknown
headquarters). The compliance page gains a flag-chip country selector
limited to the countries defined in the models catalogue, and public
per-country provider pages are generated under /providers/country/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUzb6QZFn2ngP6eohSDPdU
Copilot AI review requested due to automatic review settings July 12, 2026 12:29
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

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

Adds provider-headquarters country restrictions to compliance policies, administration controls, provider compliance checks, and public country-specific provider directory pages with metadata and sitemap entries.

Changes

Provider headquarters country support

Layer / File(s) Summary
Country policy and catalogue support
packages/models/src/providers.ts, packages/models/src/compliance.spec.ts
Adds allowedCountries enforcement, SOC 2 Type 2 checks, provider country metadata and flag helpers, plus coverage for filtering, fail-closed behavior, composition, sorting, and flags.
Compliance validation and administration
apps/api/src/routes/organization.ts, apps/ui/src/app/dashboard/.../compliance-client.tsx, apps/docs/content/features/compliance.mdx, apps/ui/src/content/changelog/..., AGENTS.md, apps/gateway/src/api.spec.ts
Validates allowed country codes, adds country selection controls, documents the policy, records the feature in the changelog, updates catalogue-enumeration guidance, and tests gateway enforcement.
Country provider directory
apps/ui/src/app/providers/country/..., apps/ui/src/components/providers/providers-grid.tsx, apps/ui/src/app/sitemap.ts
Adds country-specific provider pages, filtered provider grids, linked headquarters labels, generated metadata, and sitemap entries.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OrganizationAdmin
  participant ComplianceClient
  participant OrganizationRoute
  participant Gateway
  participant isProviderCompliant
  OrganizationAdmin->>ComplianceClient: Select headquarters countries
  ComplianceClient->>OrganizationRoute: Save allowedCountries
  OrganizationRoute->>Gateway: Apply saved policy to request
  Gateway->>isProviderCompliant: Evaluate provider policy
  isProviderCompliant-->>Gateway: Compliance result
  Gateway-->>OrganizationAdmin: Allow request or return 403
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 80.00% 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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding a provider headquarters filter to compliance.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/provider-headquarters-compliance

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.

Add compliance-requirement and headquarters-country filters to the
public providers directory, reusing isProviderCompliant. Stop
hardcoding country lists in the compliance docs and changelog and link
to the providers directory instead, and document the no-hardcoded-lists
rule in AGENTS.md (with an image/video-generation exception).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUzb6QZFn2ngP6eohSDPdU

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/models/src/providers.ts (1)

1565-1615: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test asserting all country names are human-readable.

PROVIDER_COUNTRY_NAMES is manually maintained. If a provider is added with a new country code but the name map isn't updated, getProviderCountries silently falls back to the raw code (e.g., "DE" instead of "Germany"). A one-line assertion in the existing getProviderCountries test would catch this regression automatically.

♻️ Suggested test addition in compliance.spec.ts
 	it("returns only distinct countries referenced by the catalogue, sorted by name", () => {
 		const countries = getProviderCountries();
 		const codes = countries.map((c) => c.code);
 		expect(new Set(codes).size).toBe(codes.length);
 		expect(codes).toContain("US");
 		expect(codes).toContain("CN");
 		expect(codes).not.toContain(null);
+		// Every country should have a human-readable name, not a raw code fallback.
+		for (const country of countries) {
+			expect(country.name).not.toBe(country.code);
+		}
 		const names = countries.map((c) => c.name);
 		expect(names).toEqual([...names].sort((a, b) => a.localeCompare(b)));
 	});
🤖 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 `@packages/models/src/providers.ts` around lines 1565 - 1615, Add a test
assertion for getProviderCountries that verifies every returned country name is
human-readable rather than a raw two-letter country code, catching missing
entries in PROVIDER_COUNTRY_NAMES. Place it in the existing getProviderCountries
test and preserve the current country and sorting assertions.
🤖 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 `@apps/ui/src/app/providers/country/`[country]/page.tsx:
- Around line 52-54: Align the country page’s model count with the stats bar in
ProvidersGrid by computing the summed per-provider model counts used there, or
by passing that value as a totalModels override to ProvidersGrid. Update the
modelCountForProviders usage and the ProvidersGrid call in the country page so
both the subheading and stats bar display the same count.

---

Nitpick comments:
In `@packages/models/src/providers.ts`:
- Around line 1565-1615: Add a test assertion for getProviderCountries that
verifies every returned country name is human-readable rather than a raw
two-letter country code, catching missing entries in PROVIDER_COUNTRY_NAMES.
Place it in the existing getProviderCountries test and preserve the current
country and sorting assertions.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cec5d6cb-1890-4175-adb1-bdc8effa5b66

📥 Commits

Reviewing files that changed from the base of the PR and between 03a5441 and 927e6a2.

📒 Files selected for processing (9)
  • apps/api/src/routes/organization.ts
  • apps/docs/content/features/compliance.mdx
  • apps/ui/src/app/dashboard/[orgId]/org/compliance/compliance-client.tsx
  • apps/ui/src/app/providers/country/[country]/page.tsx
  • apps/ui/src/app/sitemap.ts
  • apps/ui/src/components/providers/providers-grid.tsx
  • apps/ui/src/content/changelog/2026-07-12-provider-headquarters-filter.md
  • packages/models/src/compliance.spec.ts
  • packages/models/src/providers.ts

Comment on lines +52 to +54
const modelCount = modelCountForProviders(
new Set(countryProviders.map((p) => p.id)),
);

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Model count in subheading may differ from the stats bar in ProvidersGrid.

modelCountForProviders counts unique models across all country providers, while ProvidersGrid computes totalModels as the sum of per-provider model counts (modelCounts[p.id]). When a model is served by multiple providers headquartered in the same country, the subheading (unique count) will show a smaller number than the stats bar (summed count), creating a visible inconsistency.

Consider passing a totalModels override to ProvidersGrid or computing the model count the same way in both places.

🔧 Suggested fix: pass model count to ProvidersGrid
 interface ProvidersGridProps {
 	countryCode?: string;
 	heading?: string;
 	subheading?: string;
+	totalModelsOverride?: number;
 }

 export function ProvidersGrid({
 	countryCode,
 	heading,
 	subheading,
+	totalModelsOverride,
 }: ProvidersGridProps = {}) {
 	// ...
 	const totalModels = visibleProviders.reduce(
 		(sum, p) => sum + (modelCounts[p.id] || 0),
 		0,
 	);
+	const displayModels = totalModelsOverride ?? totalModels;
 	// Use displayModels in the stats bar and default subheading

Then in the page:

 			<ProvidersGrid
 				countryCode={country.code}
 				heading={`${country.flag} AI Providers in ${country.name}`}
 				subheading={`Access ${modelCount} models from ${countryProviders.length} AI ${
 					countryProviders.length === 1 ? "provider" : "providers"
 				} headquartered in ${country.name} through our unified API`}
+				totalModelsOverride={modelCount}
 			/>
🤖 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 `@apps/ui/src/app/providers/country/`[country]/page.tsx around lines 52 - 54,
Align the country page’s model count with the stats bar in ProvidersGrid by
computing the summed per-provider model counts used there, or by passing that
value as a totalModels override to ProvidersGrid. Update the
modelCountForProviders usage and the ProvidersGrid call in the country page so
both the subheading and stats bar display the same count.

@steebchen steebchen changed the title feat(compliance): filter providers by headquarters feat(compliance): provider headquarters filter Jul 12, 2026
@steebchen

Copy link
Copy Markdown
Member Author

Images automagically compressed by Calibre's image-actions ✨

Compression reduced images by 78.7%, saving 744.3 KB.

Filename Before After Improvement Visual comparison
apps/ui/public/changelog/provider-headquarters-filter.png 945.3 KB 201.1 KB 78.7% View diff

@steebchen

Copy link
Copy Markdown
Member Author

Images automagically compressed by Calibre's image-actions ✨

Compression reduced images by 20.9%, saving 42.1 KB.

Filename Before After Improvement Visual comparison
apps/ui/public/changelog/provider-headquarters-filter.png 201.1 KB 159.0 KB 20.9% View diff

@steebchen

Copy link
Copy Markdown
Member Author

Images automagically compressed by Calibre's image-actions ✨

Compression reduced images by 13.8%, saving 22.0 KB.

Filename Before After Improvement Visual comparison
apps/ui/public/changelog/provider-headquarters-filter.png 159.0 KB 137.0 KB 13.8% View diff

@steebchen

Copy link
Copy Markdown
Member Author

Images automagically compressed by Calibre's image-actions ✨

Compression reduced images by 71.5%, saving 1.5 MB.

Filename Before After Improvement Visual comparison
apps/ui/public/changelog/provider-headquarters-filter.png 2.1 MB 615.1 KB 71.5% View diff

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUzb6QZFn2ngP6eohSDPdU

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

🧹 Nitpick comments (1)
apps/gateway/src/api.spec.ts (1)

741-827: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting shared test setup to reduce duplication.

Both test cases share nearly identical org update, API key insert, provider key insert, and request structures — differing only in allowedCountries, token IDs, and expected status. Extracting a helper would improve maintainability if more country-policy cases are added later.

♻️ Optional refactor: shared helper for country compliance tests
+async function setupCountryComplianceTest(opts: {
+	allowedCountries: string[];
+	tokenSuffix: string;
+}) {
+	await db
+		.update(tables.organization)
+		.set({
+			plan: "enterprise",
+			providerCompliancePolicy: { enabled: true, allowedCountries: opts.allowedCountries },
+		})
+		.where(eq(tables.organization.id, "org-id"));
+
+	const suffix = opts.tokenSuffix;
+	await db.insert(tables.apiKey).values({
+		id: `token-id-${suffix}`,
+		token: `real-token-${suffix}`,
+		projectId: "project-id",
+		description: "Test API Key",
+		createdBy: "user-id",
+	});
+
+	await db.insert(tables.providerKey).values({
+		id: `provider-key-id-${suffix}`,
+		token: "sk-test-key",
+		provider: "openai",
+		organizationId: "org-id",
+		baseUrl: mockServerUrl,
+	});
+
+	return `real-token-${suffix}`;
+}
+
+async function requestChatCompletions(token: string) {
+	return app.request("/v1/chat/completions", {
+		method: "POST",
+		headers: {
+			"Content-Type": "application/json",
+			Authorization: `Bearer ${token}`,
+			"x-no-fallback": "true",
+		},
+		body: JSON.stringify({
+			model: "openai/gpt-4o",
+			messages: [{ role: "user", content: "Hello country!" }],
+		}),
+	});
+}
+
 test("/v1/chat/completions blocks providers outside the allowed countries", async () => {
 	// OpenAI is headquartered in the US, so an allowedCountries policy that only
 	// permits France removes it, leaving no provider for the pinned model.
-	await db
-		.update(tables.organization)
-		.set({
-			plan: "enterprise",
-			providerCompliancePolicy: { enabled: true, allowedCountries: ["FR"] },
-		})
-		.where(eq(tables.organization.id, "org-id"));
-
-	await db.insert(tables.apiKey).values({
-		id: "token-id-compliance-country-block",
-		token: "real-token-compliance-country-block",
-		projectId: "project-id",
-		description: "Test API Key",
-		createdBy: "user-id",
-	});
-
-	await db.insert(tables.providerKey).values({
-		id: "provider-key-id-compliance-country-block",
-		token: "sk-test-key",
-		provider: "openai",
-		organizationId: "org-id",
-		baseUrl: mockServerUrl,
-	});
-
-	const res = await app.request("/v1/chat/completions", {
-		method: "POST",
-		headers: {
-			"Content-Type": "application/json",
-			Authorization: "Bearer real-token-compliance-country-block",
-			"x-no-fallback": "true",
-		},
-		body: JSON.stringify({
-			model: "openai/gpt-4o",
-			messages: [{ role: "user", content: "Hello country!" }],
-		}),
-	});
+	const token = await setupCountryComplianceTest({
+		allowedCountries: ["FR"],
+		tokenSuffix: "compliance-country-block",
+	});
+	const res = await requestChatCompletions(token);

 	expect(res.status).toBe(403);
 	const json = await res.json();
 	expect(json.error.message).toContain("provider compliance policy");
 });

 test("/v1/chat/completions allows providers within the allowed countries", async () => {
 	// OpenAI is headquartered in the US, so an allowedCountries policy that
 	// permits the US lets it through.
-	await db
-		.update(tables.organization)
-		.set({
-			plan: "enterprise",
-			providerCompliancePolicy: { enabled: true, allowedCountries: ["US"] },
-		})
-		.where(eq(tables.organization.id, "org-id"));
-
-	await db.insert(tables.apiKey).values({
-		id: "token-id-compliance-country-allow",
-		token: "real-token-compliance-country-allow",
-		projectId: "project-id",
-		description: "Test API Key",
-		createdBy: "user-id",
-	});
-
-	await db.insert(tables.providerKey).values({
-		id: "provider-key-id-compliance-country-allow",
-		token: "sk-test-key",
-		provider: "openai",
-		organizationId: "org-id",
-		baseUrl: mockServerUrl,
-	});
-
-	const res = await app.request("/v1/chat/completions", {
-		method: "POST",
-		headers: {
-			"Content-Type": "application/json",
-			Authorization: "Bearer real-token-compliance-country-allow",
-			"x-no-fallback": "true",
-		},
-		body: JSON.stringify({
-			model: "openai/gpt-4o",
-			messages: [{ role: "user", content: "Hello country!" }],
-		}),
-	});
+	const token = await setupCountryComplianceTest({
+		allowedCountries: ["US"],
+		tokenSuffix: "compliance-country-allow",
+	});
+	const res = await requestChatCompletions(token);

 	expect(res.status).toBe(200);
 });
🤖 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 `@apps/gateway/src/api.spec.ts` around lines 741 - 827, Extract the duplicated
setup and request logic from the country-policy tests into a shared helper,
parameterized by allowedCountries, token/key identifiers, and expected response
status. Update the tests “blocks providers outside the allowed countries” and
“allows providers within the allowed countries” to use the helper while
preserving their distinct policy values and assertions.

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.

Nitpick comments:
In `@apps/gateway/src/api.spec.ts`:
- Around line 741-827: Extract the duplicated setup and request logic from the
country-policy tests into a shared helper, parameterized by allowedCountries,
token/key identifiers, and expected response status. Update the tests “blocks
providers outside the allowed countries” and “allows providers within the
allowed countries” to use the helper while preserving their distinct policy
values and assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 48170a45-371e-4b49-9dce-9198d455e956

📥 Commits

Reviewing files that changed from the base of the PR and between a781bf8 and 77dabb9.

⛔ Files ignored due to path filters (1)
  • apps/ui/public/changelog/provider-headquarters-filter.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • apps/gateway/src/api.spec.ts

claude added 5 commits July 12, 2026 14:24
Fail when a provider headquarters code lacks a display name or a valid
flag emoji, so future country additions to the catalogue force the
mappings to be updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUzb6QZFn2ngP6eohSDPdU
The requireSoc2 flag accepted any SOC 2 report while the UI labelled it
"Type 2", so a Type 1 provider slipped through. Relabel requireSoc2 as
"any type" and add a strict requireSoc2Type2 requirement (soc2 === 2),
enforced through the same isProviderCompliant chokepoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUzb6QZFn2ngP6eohSDPdU
Existing requireSoc2 policies were labelled "Type 2" in the UI, so treat
them as Type 2: a data migration converts stored requireSoc2:true to
requireSoc2Type2:true. Also make the "SOC 2 or ISO 27001" toggle require
SOC 2 Type 2 (not any type) or ISO 27001.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUzb6QZFn2ngP6eohSDPdU
…rters-compliance

# Conflicts:
#	packages/db/migrations/meta/_journal.json
@steebchen
steebchen added this pull request to the merge queue Jul 13, 2026
Merged via the queue into main with commit 96a35e6 Jul 13, 2026
17 of 18 checks passed
@steebchen
steebchen deleted the feat/provider-headquarters-compliance branch July 13, 2026 12:25
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.

3 participants