From b5c4b6d156de18ac70394a3cd728508ca12bb2c7 Mon Sep 17 00:00:00 2001 From: Christopher Sellers <1@christophersellers.com> Date: Wed, 28 Oct 2026 05:13:20 -0700 Subject: [PATCH 1/5] feat(trust): split E14 subprocessors and add DPA region/transfer fields Update the public Trust inventory for diligence E14: keep in-production vendors separate from OpenAI, Vertex, and LlamaCloud (not enabled in production); add region and transferMechanism filled only from each vendor's official DPA or regions pages; leave blank when unstated. LlamaCloud availabilityLabel matches Security/Lead draft wording. Signed-off-by: Christopher Sellers <1@christophersellers.com> --- .../lib/__tests__/trust-subprocessors.test.ts | 93 ++++++++++++ frontend/src/lib/trust-subprocessors.ts | 140 ++++++++++++++---- 2 files changed, 202 insertions(+), 31 deletions(-) create mode 100644 frontend/src/lib/__tests__/trust-subprocessors.test.ts diff --git a/frontend/src/lib/__tests__/trust-subprocessors.test.ts b/frontend/src/lib/__tests__/trust-subprocessors.test.ts new file mode 100644 index 000000000..91d5c9931 --- /dev/null +++ b/frontend/src/lib/__tests__/trust-subprocessors.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, it } from 'vitest'; +import { + TRUST_SUBPROCESSORS, + type TrustSubprocessor, +} from '../trust-subprocessors'; + +const IN_PRODUCTION_NAMES = [ + 'Supabase', + 'Railway', + 'Vercel', + 'Sentry', + 'Stripe', + 'PostHog', + 'Resend', + 'TypeSafe AI', + 'Cloudflare R2', +] as const; + +const NOT_IN_PRODUCTION_NAMES = [ + 'OpenAI', + 'Google Cloud Vertex AI', + 'LlamaCloud', +] as const; + +describe('TRUST_SUBPROCESSORS', () => { + it('includes every required vendor exactly once', () => { + const names = TRUST_SUBPROCESSORS.map((sp) => sp.name); + for (const name of [...IN_PRODUCTION_NAMES, ...NOT_IN_PRODUCTION_NAMES]) { + expect(names.filter((n) => n === name)).toHaveLength(1); + } + }); + + it('marks OpenAI, Vertex, and LlamaCloud as not_in_production', () => { + for (const name of NOT_IN_PRODUCTION_NAMES) { + const row = TRUST_SUBPROCESSORS.find((sp) => sp.name === name); + expect(row?.availability).toBe('not_in_production'); + } + }); + + it('does not use where_configured for vendors that are not in production', () => { + for (const name of NOT_IN_PRODUCTION_NAMES) { + const row = TRUST_SUBPROCESSORS.find((sp) => sp.name === name); + expect(row?.availability).not.toBe('where_configured'); + } + }); + + it('uses the exact LlamaCloud availabilityLabel from diligence steering', () => { + const llama = TRUST_SUBPROCESSORS.find((sp) => sp.name === 'LlamaCloud'); + expect(llama?.availabilityLabel).toBe( + 'available where configured; not enabled in production as of Oct 28, 2026', + ); + }); + + it('uses the Legal not-in-production label for OpenAI and Vertex', () => { + for (const name of ['OpenAI', 'Google Cloud Vertex AI'] as const) { + const row = TRUST_SUBPROCESSORS.find((sp) => sp.name === name); + expect(row?.availabilityLabel).toBe( + 'Not used in production; may be enabled only after notice', + ); + } + }); + + it('exposes region and transferMechanism fields on every row', () => { + for (const sp of TRUST_SUBPROCESSORS) { + expect(sp).toHaveProperty('region'); + expect(sp).toHaveProperty('transferMechanism'); + expect(typeof sp.region).toBe('string'); + expect(typeof sp.transferMechanism).toBe('string'); + } + }); + + it('keeps availabilityLabel free of en/em dashes', () => { + for (const sp of TRUST_SUBPROCESSORS) { + expect(sp.availabilityLabel).not.toMatch(/[\u2013\u2014]/); + expect(sp.purpose).not.toMatch(/[\u2013\u2014]/); + expect(sp.dataProcessed).not.toMatch(/[\u2013\u2014]/); + expect(sp.location).not.toMatch(/[\u2013\u2014]/); + expect(sp.region).not.toMatch(/[\u2013\u2014]/); + expect(sp.transferMechanism).not.toMatch(/[\u2013\u2014]/); + } + }); + + it('requires non-empty required display fields', () => { + for (const sp of TRUST_SUBPROCESSORS as TrustSubprocessor[]) { + expect(sp.name.length).toBeGreaterThan(0); + expect(sp.purpose.length).toBeGreaterThan(0); + expect(sp.dataProcessed.length).toBeGreaterThan(0); + expect(sp.location.length).toBeGreaterThan(0); + expect(sp.link.startsWith('https://')).toBe(true); + expect(sp.availabilityLabel.length).toBeGreaterThan(0); + } + }); +}); diff --git a/frontend/src/lib/trust-subprocessors.ts b/frontend/src/lib/trust-subprocessors.ts index e00fcf90d..ea8e05352 100644 --- a/frontend/src/lib/trust-subprocessors.ts +++ b/frontend/src/lib/trust-subprocessors.ts @@ -1,30 +1,53 @@ -export type SubprocessorAvailability = 'default' | 'where_configured'; +export type SubprocessorAvailability = + | 'default' + | 'where_configured' + | 'not_in_production'; export interface TrustSubprocessor { name: string; purpose: string; dataProcessed: string; location: string; + /** Processing region stated on the vendor's official DPA or regions page. Empty when unstated. */ + region: string; + /** Cross-border transfer mechanism stated on the vendor's official DPA. Empty when unstated. */ + transferMechanism: string; link: string; availability: SubprocessorAvailability; availabilityLabel: string; } +/** + * Public Trust / DPA subprocessor inventory. + * In production use: Supabase, Railway, Vercel, Sentry, Stripe, PostHog, Resend, TypeSafe AI, Cloudflare R2. + * Not enabled in production as of Oct 28, 2026: OpenAI, Google Cloud Vertex AI, LlamaCloud. + * region and transferMechanism are filled only from each vendor's official DPA / regions pages. + */ export const TRUST_SUBPROCESSORS: TrustSubprocessor[] = [ { name: 'Supabase', - purpose: 'Authentication for developer-portal accounts and a legacy session-validation path; stores developer-portal API-key records and pre-sales assessment leads. Core tenant workspace data is stored in PostgreSQL managed by Railway.', - dataProcessed: 'Developer-portal account records, API-key records, pre-sales assessment leads, and authentication metadata.', + purpose: + 'Authentication for developer-portal accounts and a legacy session-validation path; stores developer-portal API-key records and pre-sales assessment leads. Core tenant workspace data is stored in PostgreSQL managed by Railway.', + dataProcessed: + 'Developer-portal account records, API-key records, pre-sales assessment leads, and authentication metadata.', location: 'United States; project region per executed agreement/current DPA', + region: 'Customer directed project region', + transferMechanism: + 'EU Standard Contractual Clauses (Module 2 and Module 3); UK International Data Transfer Addendum; Swiss Addendum', link: 'https://supabase.com/privacy', availability: 'default', availabilityLabel: 'Default platform processor', }, { name: 'Railway', - purpose: 'Platform hosting the RegEngine application and the core PostgreSQL database for tenant workspace data.', - dataProcessed: 'Workspace records, evidence ledger rows, API requests, application logs, and workflow metadata.', + purpose: + 'Platform hosting the RegEngine application and the core PostgreSQL database for tenant workspace data.', + dataProcessed: + 'Workspace records, evidence ledger rows, API requests, application logs, and workflow metadata.', location: 'United States', + region: 'United States', + transferMechanism: + 'EU U.S. Data Privacy Framework and/or EU Standard Contractual Clauses with UK International Data Transfer Addendum', link: 'https://railway.app/legal/privacy', availability: 'default', availabilityLabel: 'Default platform processor', @@ -34,15 +57,23 @@ export const TRUST_SUBPROCESSORS: TrustSubprocessor[] = [ purpose: 'Frontend hosting, edge delivery, and authenticated request routing.', dataProcessed: 'Authenticated web requests, CDN metadata, and limited edge logs.', location: 'United States / global CDN', + region: 'United States', + transferMechanism: + 'EU Standard Contractual Clauses with UK International Data Transfer Agreement', link: 'https://vercel.com/legal/privacy-policy', availability: 'default', availabilityLabel: 'Default platform processor', }, { name: 'Sentry', - purpose: 'Server, edge, and backend error monitoring with PII scrubbing configured where available.', - dataProcessed: 'Error events, stack traces, request metadata, and environment diagnostics. Browser replay capture is not enabled by default.', + purpose: + 'Server, edge, and backend error monitoring with PII scrubbing configured where available.', + dataProcessed: + 'Error events, stack traces, request metadata, and environment diagnostics. Browser replay capture is not enabled by default.', location: 'United States', + region: 'United States and other countries where Sentry or its subprocessors maintain operations', + transferMechanism: + 'EU U.S. Data Privacy Framework; EU Standard Contractual Clauses with UK International Data Transfer Addendum and Swiss adaptations', link: 'https://sentry.io/privacy/', availability: 'where_configured', availabilityLabel: 'Where configured', @@ -50,8 +81,12 @@ export const TRUST_SUBPROCESSORS: TrustSubprocessor[] = [ { name: 'Stripe', purpose: 'Billing, invoices, payment processing, and tax support.', - dataProcessed: 'Billing contact information, subscription metadata, payment processor tokens, and invoice history.', + dataProcessed: + 'Billing contact information, subscription metadata, payment processor tokens, and invoice history.', location: 'United States', + region: 'United States', + transferMechanism: + 'EU U.S. Data Privacy Framework; EEA Standard Contractual Clauses; UK International Data Transfer Addendum', link: 'https://stripe.com/privacy', availability: 'where_configured', availabilityLabel: 'Where billing is enabled', @@ -59,44 +94,39 @@ export const TRUST_SUBPROCESSORS: TrustSubprocessor[] = [ { name: 'PostHog', purpose: 'Privacy-conscious product analytics after consent or workspace enablement.', - dataProcessed: 'Workspace-scoped product events, pseudonymous identifiers, and consent metadata.', + dataProcessed: + 'Workspace-scoped product events, pseudonymous identifiers, and consent metadata.', location: 'United States', + region: 'Customer selected data center location', + transferMechanism: + 'EU U.S. Data Privacy Framework (including UK Extension and Swiss U.S.); EU Standard Contractual Clauses with UK International Data Transfer Addendum and Swiss adaptations', link: 'https://posthog.com/privacy', availability: 'where_configured', availabilityLabel: 'Where analytics is enabled', }, { name: 'Resend', - purpose: 'Transactional email, including invitations, alerts, passwordless sign-in messages, and Supabase Auth delivery (password reset, magic-link, and one-time-code emails) via SMTP where configured.', + purpose: + 'Transactional email, including invitations, alerts, passwordless sign-in messages, and Supabase Auth delivery (password reset, magic-link, and one-time-code emails) via SMTP where configured.', dataProcessed: 'Recipient email addresses, message metadata, and delivery events.', location: 'United States', + region: 'United States', + transferMechanism: + 'EU Standard Contractual Clauses (Module 2 and Module 3) with UK Addendum; EU U.S. Data Privacy Framework and UK Extension', link: 'https://resend.com/legal/privacy-policy', availability: 'where_configured', availabilityLabel: 'Where email is configured', }, - { - name: 'OpenAI', - purpose: 'AI-assisted document extraction, OCR enrichment, and classification workflows.', - dataProcessed: 'Uploaded document text, label images, and extracted field candidates submitted for enabled AI workflows.', - location: 'United States / multi-region', - link: 'https://openai.com/policies/privacy-policy', - availability: 'where_configured', - availabilityLabel: 'Where AI extraction is enabled', - }, - { - name: 'Google Cloud Vertex AI', - purpose: 'Gemini-assisted extraction and classification for configured AI workflows.', - dataProcessed: 'Uploaded document text, label images, and extracted field candidates submitted for enabled AI workflows.', - location: 'United States (us-central1)', - link: 'https://cloud.google.com/terms/cloud-privacy-notice', - availability: 'where_configured', - availabilityLabel: 'Where AI extraction is enabled', - }, { name: 'TypeSafe AI', - purpose: 'Calibrated classification of CSV column headers during CSV ingestion (Jev System One model): which canonical FSMA 204 field an unrecognised column holds, and, for a file that declares no critical tracking event, which one its rows record.', - dataProcessed: 'CSV column headers; when the uploader declares a single CTE for the file, that canonical CTE name (for example "shipping"); and, only where sample values are explicitly enabled, PII-redacted sample cell values for unrecognised columns. Never whole rows or files.', + purpose: + 'Calibrated classification of CSV column headers during CSV ingestion (Jev System One model): which canonical FSMA 204 field an unrecognised column holds, and, for a file that declares no critical tracking event, which one its rows record.', + dataProcessed: + 'CSV column headers; when the uploader declares a single CTE for the file, that canonical CTE name (for example "shipping"); and, only where sample values are explicitly enabled, PII-redacted sample cell values for unrecognised columns. Never whole rows or files.', location: 'United States', + region: '', + transferMechanism: + 'EU Standard Contractual Clauses (Module 2 and Module 3); UK International Data Transfer Addendum', link: 'https://typesafe.ai/legal/privacy', availability: 'where_configured', availabilityLabel: 'Where AI CSV suggestions are enabled', @@ -104,10 +134,58 @@ export const TRUST_SUBPROCESSORS: TrustSubprocessor[] = [ { name: 'Cloudflare R2', purpose: 'Object storage for uploaded evidence files and generated artifacts.', - dataProcessed: 'Raw uploads, normalized JSON, parsed text extracts, and generated artifacts.', + dataProcessed: + 'Raw uploads, normalized JSON, parsed text extracts, and generated artifacts.', location: 'United States / global edge', + region: '', + transferMechanism: + 'EU U.S. Data Privacy Framework (including UK Extension and Swiss U.S.); EU Standard Contractual Clauses (Module 2 and Module 3) with UK International Data Transfer Addendum', link: 'https://www.cloudflare.com/privacypolicy/', availability: 'where_configured', availabilityLabel: 'Where object storage is configured', }, + { + name: 'OpenAI', + purpose: + 'AI-assisted document extraction, OCR enrichment, and classification workflows.', + dataProcessed: + 'Uploaded document text, label images, and extracted field candidates submitted for enabled AI workflows.', + location: 'United States / multi-region', + region: '', + transferMechanism: + 'EU Standard Contractual Clauses for EEA and Swiss transfers; UK International Data Transfer Addendum for UK transfers', + link: 'https://openai.com/policies/privacy-policy', + availability: 'not_in_production', + availabilityLabel: 'Not used in production; may be enabled only after notice', + }, + { + name: 'Google Cloud Vertex AI', + purpose: 'Gemini-assisted extraction and classification for configured AI workflows.', + dataProcessed: + 'Uploaded document text, label images, and extracted field candidates submitted for enabled AI workflows.', + location: 'United States (us-central1)', + region: 'Customer selected Google Cloud region', + transferMechanism: + 'EU Standard Contractual Clauses or an Alternative Transfer Solution adopted by Google', + link: 'https://cloud.google.com/terms/cloud-privacy-notice', + availability: 'not_in_production', + availabilityLabel: 'Not used in production; may be enabled only after notice', + }, + { + name: 'LlamaCloud', + purpose: + 'Optional second pass document extraction for supplier PDFs on the bulk upload path when RegEngine table parsing finds no structured rows. Only runs if a LlamaCloud API key is configured.', + dataProcessed: + 'Complete uploaded document file content sent for an extraction job, plus extraction outputs returned to RegEngine. RegEngine attempts to delete the vendor side file when the job ends and disables vendor result caching by default.', + location: + 'United States by default (LlamaCloud NA). EU Frankfurt only if an EU endpoint is configured.', + region: + 'United States (AWS us-east-1) by default; European Union Frankfurt (AWS eu-central-1) when using the EU endpoint', + transferMechanism: + 'EU Standard Contractual Clauses (Module 2 and Module 3) and UK International Data Transfer Addendum (self serve DPA incorporated via Terms of Service)', + link: 'https://www.llamaindex.ai/legal/terms-of-service', + availability: 'not_in_production', + availabilityLabel: + 'available where configured; not enabled in production as of Oct 28, 2026', + }, ]; From 51100123845ae8fcb484dd606d0f62e704023341 Mon Sep 17 00:00:00 2001 From: Christopher Sellers <1@christophersellers.com> Date: Wed, 28 Oct 2026 05:48:27 -0700 Subject: [PATCH 2/5] fix(trust): mark LlamaCloud as planned not live for E14 Update the LlamaCloud availability label to the planned wording and reword purpose so the draft does not claim the vendor is live. Signed-off-by: Christopher Sellers <1@christophersellers.com> --- frontend/src/lib/__tests__/trust-subprocessors.test.ts | 2 +- frontend/src/lib/trust-subprocessors.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/__tests__/trust-subprocessors.test.ts b/frontend/src/lib/__tests__/trust-subprocessors.test.ts index 91d5c9931..2ac9e04a0 100644 --- a/frontend/src/lib/__tests__/trust-subprocessors.test.ts +++ b/frontend/src/lib/__tests__/trust-subprocessors.test.ts @@ -47,7 +47,7 @@ describe('TRUST_SUBPROCESSORS', () => { it('uses the exact LlamaCloud availabilityLabel from diligence steering', () => { const llama = TRUST_SUBPROCESSORS.find((sp) => sp.name === 'LlamaCloud'); expect(llama?.availabilityLabel).toBe( - 'available where configured; not enabled in production as of Oct 28, 2026', + 'Planned: document extraction for admin bulk PDF uploads; not enabled in production as of Oct 28, 2026; enablement pending legal review and customer notice', ); }); diff --git a/frontend/src/lib/trust-subprocessors.ts b/frontend/src/lib/trust-subprocessors.ts index ea8e05352..7323dd26d 100644 --- a/frontend/src/lib/trust-subprocessors.ts +++ b/frontend/src/lib/trust-subprocessors.ts @@ -174,7 +174,7 @@ export const TRUST_SUBPROCESSORS: TrustSubprocessor[] = [ { name: 'LlamaCloud', purpose: - 'Optional second pass document extraction for supplier PDFs on the bulk upload path when RegEngine table parsing finds no structured rows. Only runs if a LlamaCloud API key is configured.', + 'Planned document extraction for admin bulk PDF uploads. Not enabled in production; enablement pending legal review and customer notice.', dataProcessed: 'Complete uploaded document file content sent for an extraction job, plus extraction outputs returned to RegEngine. RegEngine attempts to delete the vendor side file when the job ends and disables vendor result caching by default.', location: @@ -186,6 +186,6 @@ export const TRUST_SUBPROCESSORS: TrustSubprocessor[] = [ link: 'https://www.llamaindex.ai/legal/terms-of-service', availability: 'not_in_production', availabilityLabel: - 'available where configured; not enabled in production as of Oct 28, 2026', + 'Planned: document extraction for admin bulk PDF uploads; not enabled in production as of Oct 28, 2026; enablement pending legal review and customer notice', }, ]; From 690aa4c71bb602c1c45f058fe6759bd9890966f2 Mon Sep 17 00:00:00 2001 From: Christopher Sellers <1@christophersellers.com> Date: Wed, 28 Oct 2026 05:55:04 -0700 Subject: [PATCH 3/5] fix(trust): correct Vercel UK transfer mechanism to Addendum Codex FAIL on #3707: Vercel DPA Schedule 3 defines UK IDTA as the UK international data transfer addendum, not Agreement. Re-verified other vendors; no other Agreement/Addendum mismatches. Signed-off-by: Christopher Sellers <1@christophersellers.com> --- frontend/src/lib/__tests__/trust-subprocessors.test.ts | 8 ++++++++ frontend/src/lib/trust-subprocessors.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/__tests__/trust-subprocessors.test.ts b/frontend/src/lib/__tests__/trust-subprocessors.test.ts index 2ac9e04a0..578194ac0 100644 --- a/frontend/src/lib/__tests__/trust-subprocessors.test.ts +++ b/frontend/src/lib/__tests__/trust-subprocessors.test.ts @@ -80,6 +80,14 @@ describe('TRUST_SUBPROCESSORS', () => { } }); + it('uses Vercel UK Addendum wording from the Vercel DPA', () => { + const vercel = TRUST_SUBPROCESSORS.find((sp) => sp.name === 'Vercel'); + expect(vercel?.transferMechanism).toBe( + 'EU Standard Contractual Clauses with UK International Data Transfer Addendum', + ); + expect(vercel?.transferMechanism).not.toMatch(/Agreement/i); + }); + it('requires non-empty required display fields', () => { for (const sp of TRUST_SUBPROCESSORS as TrustSubprocessor[]) { expect(sp.name.length).toBeGreaterThan(0); diff --git a/frontend/src/lib/trust-subprocessors.ts b/frontend/src/lib/trust-subprocessors.ts index 7323dd26d..6c8db0a8a 100644 --- a/frontend/src/lib/trust-subprocessors.ts +++ b/frontend/src/lib/trust-subprocessors.ts @@ -59,7 +59,7 @@ export const TRUST_SUBPROCESSORS: TrustSubprocessor[] = [ location: 'United States / global CDN', region: 'United States', transferMechanism: - 'EU Standard Contractual Clauses with UK International Data Transfer Agreement', + 'EU Standard Contractual Clauses with UK International Data Transfer Addendum', link: 'https://vercel.com/legal/privacy-policy', availability: 'default', availabilityLabel: 'Default platform processor', From cc8694884af18a337b54f5460ee1bff3a6a786f5 Mon Sep 17 00:00:00 2001 From: Christopher Sellers <1@christophersellers.com> Date: Wed, 28 Oct 2026 07:41:56 -0700 Subject: [PATCH 4/5] fix(copy): reword Trust Preview status so it no longer reads as a GA claim The Preview status detail on /trust said "but is not GA" in the same line as GS1 EPCIS 2.0, which the copy gate's live-claim rule (correctly) treats as labeling a non-live capability with GA. It fails on main too (f8d7cdf25). Reword to "has not reached general release" with the same meaning. Also ratchet scripts/copy-lint/baseline.json down for the 10 file/rule entries that #3641 already fixed on main (counts only decrease to 0; nothing is added or raised). Signed-off-by: Christopher Sellers <1@christophersellers.com> --- frontend/src/app/trust/page.tsx | 2 +- scripts/copy-lint/baseline.json | 26 -------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/frontend/src/app/trust/page.tsx b/frontend/src/app/trust/page.tsx index ad7269059..2637f9a13 100644 --- a/frontend/src/app/trust/page.tsx +++ b/frontend/src/app/trust/page.tsx @@ -50,7 +50,7 @@ const STATUS_SUMMARY = [ }, { label: 'Preview', - detail: 'Capability is usable for evaluation in supported workspaces, but is not GA. GS1 EPCIS 2.0 export and ingest stay Preview until separately promoted.', + detail: 'Capability is usable for evaluation in supported workspaces, but has not reached general release. GS1 EPCIS 2.0 export and ingest stay Preview until separately promoted.', }, { label: 'Pilot', diff --git a/scripts/copy-lint/baseline.json b/scripts/copy-lint/baseline.json index a9272faea..6d091f71f 100644 --- a/scripts/copy-lint/baseline.json +++ b/scripts/copy-lint/baseline.json @@ -2,22 +2,12 @@ "frontend/src/app/auth/verify/page.tsx": { "empty-render": 2 }, - "frontend/src/app/blog/fda-food-traceability-requirements/page.tsx": { - "epcis-unlabeled": 1 - }, "frontend/src/app/blog/food-traceability-software/page.tsx": { - "epcis-unlabeled": 1, "sample-demo": 2 }, "frontend/src/app/blog/fsma-204-compliance-checklist-2026/page.tsx": { "invented-entity": 2 }, - "frontend/src/app/blog/fsma-204-compliance-guide/page.tsx": { - "epcis-unlabeled": 2 - }, - "frontend/src/app/blog/fsma-204-cte-kde-guide/page.tsx": { - "epcis-unlabeled": 5 - }, "frontend/src/app/contact/layout.tsx": { "sample-demo": 2 }, @@ -38,7 +28,6 @@ "empty-render": 1 }, "frontend/src/app/design-system/page.tsx": { - "invented-entity": 1, "sample-demo": 1, "sandbox": 1 }, @@ -58,9 +47,6 @@ "dash-placeholder": 3, "empty-render": 2 }, - "frontend/src/app/fsma-204/[cte]/page.tsx": { - "invented-entity": 1 - }, "frontend/src/app/identity/page.tsx": { "dash-placeholder": 2, "empty-render": 1 @@ -78,9 +64,6 @@ "frontend/src/app/legal/vulnerability-disclosure/page.tsx": { "sample-demo": 1 }, - "frontend/src/app/product/page.tsx": { - "sandbox": 2 - }, "frontend/src/app/records/page.tsx": { "dash-placeholder": 3, "empty-render": 1 @@ -100,9 +83,6 @@ "frontend/src/app/tools/tlc-validator/components/TLCValidatorClient.tsx": { "sample-demo": 1 }, - "frontend/src/app/why-regengine/page.tsx": { - "epcis-unlabeled": 1 - }, "frontend/src/components/dashboard/compliance-status-widget.tsx": { "empty-render": 1 }, @@ -133,9 +113,6 @@ "frontend/src/lib/dev-compliance-snapshots.ts": { "dash-placeholder": 2 }, - "frontend/src/lib/epcis-upload.ts": { - "epcis-unlabeled": 4 - }, "frontend/src/lib/mock-tenants.ts": { "sample-demo": 1 }, @@ -144,8 +121,5 @@ }, "frontend/src/lib/trust-subprocessors.ts": { "sample-demo": 1 - }, - "frontend/src/lib/utils.ts": { - "dash-placeholder": 1 } } From 8bcb95a7fb4a43a712d465f8fc009a541bbd8c67 Mon Sep 17 00:00:00 2001 From: Christopher Sellers <1@christophersellers.com> Date: Wed, 28 Oct 2026 07:43:07 -0700 Subject: [PATCH 5/5] test(e2e): wait on SQLite busy lock in invite journey fixture helpers The invite journey passed its assertions (the invitee is listed on /settings/users) but then failed in cleanupInviteFixture with "database is locked" on both attempts. The spec opened the admin SQLite file with node:sqlite and no busy timeout, so any in-flight write from the admin service made SQLite return SQLITE_BUSY immediately. Open the database through one helper that sets PRAGMA busy_timeout=5000 so the fixture waits for the concurrent write to commit. No assertion, skip or retry setting changes. Signed-off-by: Christopher Sellers <1@christophersellers.com> --- .../tests/e2e/authenticated-core-journeys.spec.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend/tests/e2e/authenticated-core-journeys.spec.ts b/frontend/tests/e2e/authenticated-core-journeys.spec.ts index 898afec55..2cb914634 100644 --- a/frontend/tests/e2e/authenticated-core-journeys.spec.ts +++ b/frontend/tests/e2e/authenticated-core-journeys.spec.ts @@ -97,8 +97,19 @@ function removeInviteEmailOutboxRecords(email: string): void { fs.writeFileSync(outbox, remaining.length > 0 ? `${remaining.join('\n')}\n` : '', 'utf-8'); } -function expectAdminAuditLog(tenantId: string, eventType: string, resourceId: string): void { +// The admin service writes to this same SQLite file while the browser is on +// /settings/users. Without a busy timeout SQLite answers SQLITE_BUSY at once +// ("database is locked") instead of waiting for that write to commit. +const ADMIN_SQLITE_BUSY_TIMEOUT_MS = 5000; + +function openAdminDatabase(): DatabaseSync { const database = new DatabaseSync(adminSqlitePath()); + database.exec(`PRAGMA busy_timeout = ${ADMIN_SQLITE_BUSY_TIMEOUT_MS}`); + return database; +} + +function expectAdminAuditLog(tenantId: string, eventType: string, resourceId: string): void { + const database = openAdminDatabase(); try { const row = database .prepare('SELECT event_type, resource_type, resource_id FROM audit_logs WHERE tenant_id = ? AND event_type = ? AND resource_id = ? LIMIT 1') @@ -113,7 +124,7 @@ function cleanupInviteFixture(tenantId: string | null, email: string, userId?: s removeInviteEmailOutboxRecords(email); if (!tenantId || !isLocalE2E()) return; - const database = new DatabaseSync(adminSqlitePath()); + const database = openAdminDatabase(); try { if (userId) { database.prepare('DELETE FROM memberships WHERE tenant_id = ? AND user_id = ?').run(tenantId, userId);