@openpolicy/sdk 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,153 @@
1
+ # PrivacyPolicyConfig Field Reference
2
+
3
+ When used inside `defineConfig()`, `company` is omitted — it lives at the top level of `OpenPolicyConfig`. All other fields below are on `privacy: Omit<PrivacyPolicyConfig, "company">`.
4
+
5
+ ## PrivacyPolicyConfig
6
+
7
+ | Field | Type | Required | Notes |
8
+ |---|---|---|---|
9
+ | `effectiveDate` | `string` | Yes | ISO date string (e.g. `"2026-01-01"`). Validation fails if empty. |
10
+ | `dataCollected` | `Record<string, string[]>` | Yes | At least one entry required. Keys are category labels; values are arrays of data point labels. Use `DataCategories.*` presets or spread `dataCollected` sentinel. |
11
+ | `legalBasis` | `LegalBasis \| LegalBasis[]` | Yes (GDPR) | Required when `"eu"` is in `jurisdictions`. Accepts a single value or array. |
12
+ | `retention` | `Record<string, string>` | Yes | Keys should match `dataCollected` categories. Use `Retention.*` preset strings. |
13
+ | `cookies` | `{ essential: boolean; analytics: boolean; marketing: boolean }` | Yes | All three booleans required. Drives cookie policy sections. |
14
+ | `thirdParties` | `{ name: string; purpose: string; policyUrl?: string }[]` | Yes | Can be empty array. Use `Providers.*` presets or spread `thirdParties` sentinel. |
15
+ | `userRights` | `UserRight[]` | Yes | Empty array triggers a warning. GDPR recommends 6 rights; CCPA recommends 4. |
16
+ | `jurisdictions` | `Jurisdiction[]` | Yes | Controls which jurisdiction-specific sections appear. See values below. |
17
+ | `children` | `{ underAge: number; noticeUrl?: string }` | No | Include when service is directed at children. `underAge` must be a positive integer. |
18
+
19
+ ### LegalBasis values
20
+
21
+ | Constant | String value |
22
+ |---|---|
23
+ | `LegalBases.Consent` | `"consent"` |
24
+ | `LegalBases.Contract` | `"contract"` |
25
+ | `LegalBases.LegalObligation` | `"legal_obligation"` |
26
+ | `LegalBases.VitalInterests` | `"vital_interests"` |
27
+ | `LegalBases.PublicTask` | `"public_task"` |
28
+ | `LegalBases.LegitimateInterests` | `"legitimate_interests"` |
29
+
30
+ ### Jurisdiction values
31
+
32
+ | Value | Region |
33
+ |---|---|
34
+ | `"us"` | United States |
35
+ | `"eu"` | European Union (triggers GDPR sections) |
36
+ | `"ca"` | California, USA (triggers CCPA sections) |
37
+ | `"au"` | Australia |
38
+ | `"nz"` | New Zealand |
39
+ | `"other"` | Other / unspecified |
40
+
41
+ ### UserRight values
42
+
43
+ | Constant | String value | Required by |
44
+ |---|---|---|
45
+ | `Rights.Access` | `"access"` | GDPR, CCPA |
46
+ | `Rights.Rectification` | `"rectification"` | GDPR |
47
+ | `Rights.Erasure` | `"erasure"` | GDPR, CCPA |
48
+ | `Rights.Portability` | `"portability"` | GDPR |
49
+ | `Rights.Restriction` | `"restriction"` | GDPR |
50
+ | `Rights.Objection` | `"objection"` | GDPR |
51
+ | `Rights.OptOutSale` | `"opt_out_sale"` | CCPA |
52
+ | `Rights.NonDiscrimination` | `"non_discrimination"` | CCPA |
53
+
54
+ ### DataCategories presets
55
+
56
+ Each entry is a `Record<string, string[]>` safe to spread into `dataCollected`:
57
+
58
+ | Constant | Category label | Data points |
59
+ |---|---|---|
60
+ | `DataCategories.AccountInfo` | `"Account Information"` | Name, Email address |
61
+ | `DataCategories.SessionData` | `"Session Data"` | IP address, User agent, Browser type |
62
+ | `DataCategories.PaymentInfo` | `"Payment Information"` | Card last 4 digits, Billing name, Billing address |
63
+ | `DataCategories.UsageData` | `"Usage Data"` | Pages visited, Features used, Time spent |
64
+ | `DataCategories.DeviceInfo` | `"Device Information"` | Device type, Operating system, Browser version |
65
+ | `DataCategories.LocationData` | `"Location Data"` | Country, City, Timezone |
66
+ | `DataCategories.Communications` | `"Communications"` | Email content, Support tickets |
67
+
68
+ ### Retention presets
69
+
70
+ | Constant | String value |
71
+ |---|---|
72
+ | `Retention.UntilAccountDeletion` | `"Until account deletion"` |
73
+ | `Retention.UntilSessionExpiry` | `"Until session expiry"` |
74
+ | `Retention.ThirtyDays` | `"30 days"` |
75
+ | `Retention.NinetyDays` | `"90 days"` |
76
+ | `Retention.OneYear` | `"1 year"` |
77
+ | `Retention.ThreeYears` | `"3 years"` |
78
+ | `Retention.AsRequiredByLaw` | `"As required by applicable law"` |
79
+
80
+ ### Providers presets
81
+
82
+ Each entry is a `{ name: string; purpose: string; policyUrl: string }` safe to use in `thirdParties`:
83
+
84
+ **Payments:** `Providers.Stripe`, `Providers.Paddle`, `Providers.LemonSqueezy`, `Providers.PayPal`
85
+
86
+ **Analytics:** `Providers.GoogleAnalytics`, `Providers.PostHog`, `Providers.Plausible`, `Providers.Mixpanel`
87
+
88
+ **Infrastructure:** `Providers.Vercel`, `Providers.Cloudflare`, `Providers.AWS`
89
+
90
+ **Auth:** `Providers.Auth0`, `Providers.Clerk`
91
+
92
+ **Email:** `Providers.Resend`, `Providers.Postmark`, `Providers.SendGrid`, `Providers.Loops`
93
+
94
+ **Monitoring:** `Providers.Sentry`, `Providers.Datadog`
95
+
96
+ ### Compliance presets
97
+
98
+ | Preset | Expands to |
99
+ |---|---|
100
+ | `Compliance.GDPR` | `{ jurisdictions: ["eu"], legalBasis: ["legitimate_interests"], userRights: ["access", "rectification", "erasure", "portability", "restriction", "objection"] }` |
101
+ | `Compliance.CCPA` | `{ jurisdictions: ["ca"], userRights: ["access", "erasure", "opt_out_sale", "non_discrimination"] }` |
102
+
103
+ ### Validation behavior
104
+
105
+ - `effectiveDate` empty → fatal error
106
+ - `company.*` fields empty → fatal error per field
107
+ - `dataCollected` has zero keys → fatal error
108
+ - `userRights` empty → warning only
109
+ - `"eu"` in jurisdictions + no `legalBasis` → fatal error
110
+ - `"eu"` in jurisdictions + missing GDPR right → warning per right
111
+ - `"ca"` in jurisdictions + missing CCPA right → warning per right
112
+ - `children.underAge` ≤ 0 → fatal error
113
+
114
+ Source: `packages/core/src/validate.ts`
115
+
116
+ ---
117
+
118
+ ## CookiePolicyConfig
119
+
120
+ When used inside `defineConfig()`, supply as `cookie: Omit<CookiePolicyConfig, "company">`.
121
+
122
+ | Field | Type | Required | Notes |
123
+ |---|---|---|---|
124
+ | `effectiveDate` | `string` | Yes | ISO date string. |
125
+ | `cookies` | `CookiePolicyCookies` | Yes | `essential: boolean` is required; all other keys are `boolean` and treated as additional cookie categories. |
126
+ | `jurisdictions` | `Jurisdiction[]` | Yes | Same values as `PrivacyPolicyConfig.jurisdictions`. |
127
+ | `thirdParties` | `{ name: string; purpose: string; policyUrl?: string }[]` | No | Use `Providers.*` presets. |
128
+ | `trackingTechnologies` | `string[]` | No | e.g. `["cookies", "localStorage", "sessionStorage", "pixel"]` |
129
+ | `consentMechanism` | `{ hasBanner: boolean; hasPreferencePanel: boolean; canWithdraw: boolean }` | No | Required when `"eu"` in jurisdictions for GDPR compliance. |
130
+
131
+ ### CookiePolicyCookies shape
132
+
133
+ ```ts
134
+ type CookiePolicyCookies = {
135
+ essential: boolean; // required
136
+ [key: string]: boolean; // additional categories
137
+ };
138
+ ```
139
+
140
+ Example with custom categories:
141
+
142
+ ```ts
143
+ cookie: {
144
+ effectiveDate: "2026-01-01",
145
+ cookies: {
146
+ essential: true,
147
+ analytics: true,
148
+ marketing: false,
149
+ preferences: true,
150
+ },
151
+ jurisdictions: ["eu", "us"],
152
+ }
153
+ ```
@@ -0,0 +1,130 @@
1
+ # TermsOfServiceConfig Field Reference
2
+
3
+ When used inside `defineConfig()`, supply as `terms: Omit<TermsOfServiceConfig, "company">`. `company` lives at the top level of `OpenPolicyConfig`.
4
+
5
+ ## Required fields
6
+
7
+ | Field | Type | Notes |
8
+ |---|---|---|
9
+ | `effectiveDate` | `string` | ISO date string. Validation fails if empty. |
10
+ | `acceptance` | `{ methods: string[] }` | How users accept — e.g. `["using the service", "creating an account"]`. Warning if `methods` is empty. |
11
+ | `governingLaw` | `{ jurisdiction: string }` | **Required.** Validation throws fatal error if `jurisdiction` is absent or empty. |
12
+
13
+ ## Optional sections
14
+
15
+ All fields below are optional. Include only those that apply to the service. Omitting a field causes the corresponding policy section to be absent from the rendered document.
16
+
17
+ | Field | Type | Notes |
18
+ |---|---|---|
19
+ | `eligibility` | `{ minimumAge: number; jurisdictionRestrictions?: string[] }` | Include when service has age or geographic restrictions. |
20
+ | `accounts` | `{ registrationRequired: boolean; userResponsibleForCredentials: boolean; companyCanTerminate: boolean }` | Include when the service has user accounts. |
21
+ | `prohibitedUses` | `string[]` | List of prohibited use descriptions. e.g. `["Scraping content without permission", "Impersonating other users"]`. |
22
+ | `userContent` | `{ usersOwnContent: boolean; licenseGrantedToCompany: boolean; licenseDescription?: string; companyCanRemoveContent: boolean }` | Include when users upload or create content. |
23
+ | `intellectualProperty` | `{ companyOwnsService: boolean; usersMayNotCopy: boolean }` | Standard IP clause. |
24
+ | `payments` | `{ hasPaidFeatures: boolean; refundPolicy?: string; priceChangesNotice?: string }` | Include when service has paid tiers or subscriptions. |
25
+ | `availability` | `{ noUptimeGuarantee: boolean; maintenanceWindows?: string }` | SaaS services typically include this. |
26
+ | `termination` | `{ companyCanTerminate: boolean; userCanTerminate: boolean; effectOfTermination?: string }` | Include to describe account closure behavior. |
27
+ | `disclaimers` | `{ serviceProvidedAsIs: boolean; noWarranties: boolean }` | Strongly recommended — validation emits a warning if absent. |
28
+ | `limitationOfLiability` | `{ excludesIndirectDamages: boolean; liabilityCap?: string }` | Strongly recommended — validation emits a warning if absent. |
29
+ | `indemnification` | `{ userIndemnifiesCompany: boolean; scope?: string }` | Include when the service exposes the company to user-generated liability. |
30
+ | `thirdPartyServices` | `{ name: string; purpose: string }[]` | Third-party services embedded in the product (note: no `policyUrl` field here, unlike privacy's `thirdParties`). |
31
+ | `disputeResolution` | `{ method: DisputeResolutionMethod; venue?: string; classActionWaiver?: boolean }` | Include to specify arbitration or litigation preference. |
32
+ | `changesPolicy` | `{ noticeMethod: string; noticePeriodDays?: number }` | Describe how users are notified of ToS changes. |
33
+ | `privacyPolicyUrl` | `string` | URL of the privacy policy. Cross-references the privacy document. |
34
+
35
+ ## DisputeResolutionMethod values
36
+
37
+ | Value | Meaning |
38
+ |---|---|
39
+ | `"arbitration"` | Binding arbitration |
40
+ | `"litigation"` | Court litigation |
41
+ | `"mediation"` | Non-binding mediation |
42
+
43
+ ## Validation behavior
44
+
45
+ - `effectiveDate` empty → fatal error
46
+ - `company.*` fields empty → fatal error per field
47
+ - `governingLaw.jurisdiction` empty or missing → fatal error
48
+ - `acceptance.methods` empty → warning only
49
+ - `disclaimers` absent → warning only
50
+ - `limitationOfLiability` absent → warning only
51
+
52
+ Source: `packages/core/src/validate-terms.ts`
53
+
54
+ ## Complete example
55
+
56
+ ```ts
57
+ import { defineConfig } from "@openpolicy/sdk";
58
+
59
+ export default defineConfig({
60
+ company: {
61
+ name: "Acme",
62
+ legalName: "Acme, Inc.",
63
+ address: "123 Main St, San Francisco, CA 94105",
64
+ contact: "legal@acme.com",
65
+ },
66
+ terms: {
67
+ effectiveDate: "2026-01-01",
68
+ acceptance: { methods: ["using the service", "creating an account"] },
69
+ governingLaw: { jurisdiction: "Delaware, USA" },
70
+ eligibility: { minimumAge: 18 },
71
+ accounts: {
72
+ registrationRequired: true,
73
+ userResponsibleForCredentials: true,
74
+ companyCanTerminate: true,
75
+ },
76
+ prohibitedUses: [
77
+ "Scraping content without written permission",
78
+ "Using the service to distribute malware",
79
+ "Impersonating other users or Acme employees",
80
+ ],
81
+ userContent: {
82
+ usersOwnContent: true,
83
+ licenseGrantedToCompany: true,
84
+ licenseDescription: "A worldwide, royalty-free license to host and display your content.",
85
+ companyCanRemoveContent: true,
86
+ },
87
+ intellectualProperty: {
88
+ companyOwnsService: true,
89
+ usersMayNotCopy: true,
90
+ },
91
+ payments: {
92
+ hasPaidFeatures: true,
93
+ refundPolicy: "30-day money-back guarantee on annual plans.",
94
+ priceChangesNotice: "30 days written notice before price changes take effect.",
95
+ },
96
+ availability: {
97
+ noUptimeGuarantee: true,
98
+ maintenanceWindows: "Scheduled maintenance occurs Sundays 02:00–04:00 UTC.",
99
+ },
100
+ termination: {
101
+ companyCanTerminate: true,
102
+ userCanTerminate: true,
103
+ effectOfTermination: "All licenses granted to the user terminate immediately upon account closure.",
104
+ },
105
+ disclaimers: { serviceProvidedAsIs: true, noWarranties: true },
106
+ limitationOfLiability: {
107
+ excludesIndirectDamages: true,
108
+ liabilityCap: "fees paid by the user in the twelve months preceding the claim",
109
+ },
110
+ indemnification: {
111
+ userIndemnifiesCompany: true,
112
+ scope: "Claims arising from user content or user violations of these Terms.",
113
+ },
114
+ thirdPartyServices: [
115
+ { name: "Stripe", purpose: "Payment processing" },
116
+ { name: "AWS", purpose: "Cloud infrastructure" },
117
+ ],
118
+ disputeResolution: {
119
+ method: "arbitration",
120
+ venue: "San Francisco, CA",
121
+ classActionWaiver: true,
122
+ },
123
+ changesPolicy: {
124
+ noticeMethod: "Email notification to the address on your account",
125
+ noticePeriodDays: 30,
126
+ },
127
+ privacyPolicyUrl: "https://acme.com/privacy",
128
+ },
129
+ });
130
+ ```
@@ -0,0 +1,251 @@
1
+ ---
2
+ name: getting-started
3
+ description: >
4
+ End-to-end setup for OpenPolicy: install @openpolicy/sdk, @openpolicy/react, and
5
+ @openpolicy/vite-auto-collect; create openpolicy.ts with defineConfig(); wire autoCollect()
6
+ into vite.config.ts; wrap the React app with <OpenPolicy>; render <PrivacyPolicy>.
7
+ type: lifecycle
8
+ library: openpolicy
9
+ library_version: "0.0.19"
10
+ sources:
11
+ - jamiedavenport/openpolicy:packages/sdk/README.md
12
+ - jamiedavenport/openpolicy:packages/react/src/context.tsx
13
+ - jamiedavenport/openpolicy:packages/vite-auto-collect/src/index.ts
14
+ ---
15
+
16
+ ## Setup
17
+
18
+ Install packages:
19
+
20
+ ```sh
21
+ bun add @openpolicy/sdk @openpolicy/react @openpolicy/vite-auto-collect
22
+ ```
23
+
24
+ Create `openpolicy.ts` at the project root:
25
+
26
+ ```ts
27
+ import { defineConfig, dataCollected, thirdParties } from "@openpolicy/sdk";
28
+
29
+ export default defineConfig({
30
+ company: {
31
+ name: "Acme",
32
+ legalName: "Acme, Inc.",
33
+ address: "123 Main St, San Francisco, CA 94105",
34
+ contact: "privacy@acme.com",
35
+ },
36
+ privacy: {
37
+ effectiveDate: "2026-01-01",
38
+ dataCollected: {
39
+ ...dataCollected,
40
+ "Account Information": ["Email address", "Display name"],
41
+ },
42
+ thirdParties: [...thirdParties],
43
+ },
44
+ });
45
+ ```
46
+
47
+ Add `autoCollect()` to `vite.config.ts` — it must appear before any React plugin:
48
+
49
+ ```ts
50
+ import { defineConfig } from "vite";
51
+ import react from "@vitejs/plugin-react";
52
+ import { autoCollect } from "@openpolicy/vite-auto-collect";
53
+
54
+ export default defineConfig({
55
+ plugins: [
56
+ autoCollect({ thirdParties: { usePackageJson: true } }),
57
+ react(),
58
+ ],
59
+ });
60
+ ```
61
+
62
+ Wrap the application root with `<OpenPolicy>`:
63
+
64
+ ```tsx
65
+ // main.tsx or _app.tsx or layout.tsx
66
+ import { OpenPolicy } from "@openpolicy/react";
67
+ import config from "./openpolicy";
68
+
69
+ export function App({ children }: { children: React.ReactNode }) {
70
+ return <OpenPolicy config={config}>{children}</OpenPolicy>;
71
+ }
72
+ ```
73
+
74
+ Render a policy page:
75
+
76
+ ```tsx
77
+ import { PrivacyPolicy } from "@openpolicy/react";
78
+ import "@openpolicy/react/styles.css";
79
+
80
+ export default function PrivacyPage() {
81
+ return <PrivacyPolicy />;
82
+ }
83
+ ```
84
+
85
+ ## Core Patterns
86
+
87
+ ### Mark data collection inline with `collecting()`
88
+
89
+ ```ts
90
+ import { collecting } from "@openpolicy/sdk";
91
+
92
+ // Call next to the point of collection; autoCollect() scans for these at build time
93
+ export async function createUser(name: string, email: string) {
94
+ const user = collecting(
95
+ "Account Information",
96
+ { name, email },
97
+ { name: "Display name", email: "Email address" },
98
+ );
99
+ return db.users.create(user);
100
+ }
101
+ ```
102
+
103
+ The category and label arguments must be string literals — dynamic variables are silently skipped by the static scanner.
104
+
105
+ ### Mark third-party integrations with `thirdParty()`
106
+
107
+ ```ts
108
+ import { thirdParty } from "@openpolicy/sdk";
109
+
110
+ // Place next to the integration's initialisation
111
+ thirdParty("Stripe", "Payment processing", "https://stripe.com/privacy");
112
+ ```
113
+
114
+ The `autoCollect({ thirdParties: { usePackageJson: true } })` option also auto-detects ~30 known packages (Stripe, Sentry, PostHog, etc.) from `package.json`.
115
+
116
+ ### Spread both sentinels in `openpolicy.ts`
117
+
118
+ ```ts
119
+ import { defineConfig, dataCollected, thirdParties } from "@openpolicy/sdk";
120
+
121
+ export default defineConfig({
122
+ company: {
123
+ name: "Acme",
124
+ legalName: "Acme, Inc.",
125
+ address: "123 Main St, San Francisco, CA 94105",
126
+ contact: "privacy@acme.com",
127
+ },
128
+ privacy: {
129
+ effectiveDate: "2026-01-01",
130
+ dataCollected: {
131
+ ...dataCollected, // populated by autoCollect() at build time
132
+ "Manual Category": ["Manually added field"], // additional hand-declared entries
133
+ },
134
+ thirdParties: [...thirdParties], // populated by autoCollect() at build time
135
+ },
136
+ });
137
+ ```
138
+
139
+ Both `dataCollected` and `thirdParties` are placeholder objects in `@openpolicy/sdk`; `autoCollect()` replaces them via virtual module injection during the Vite build.
140
+
141
+ ## Common Mistakes
142
+
143
+ ### CRITICAL: Using `openPolicy()` from `@openpolicy/vite` instead of `autoCollect()`
144
+
145
+ Wrong:
146
+ ```ts
147
+ // vite.config.ts
148
+ import { openPolicy } from "@openpolicy/vite";
149
+
150
+ export default defineConfig({
151
+ plugins: [openPolicy({ formats: ["markdown"], outDir: "public/policies" })],
152
+ });
153
+ ```
154
+
155
+ Correct:
156
+ ```ts
157
+ // vite.config.ts
158
+ import { autoCollect } from "@openpolicy/vite-auto-collect";
159
+
160
+ export default defineConfig({
161
+ plugins: [autoCollect()],
162
+ });
163
+ ```
164
+
165
+ `openPolicy()` generates static files at build time that React components never read; `autoCollect()` populates the `dataCollected` and `thirdParties` sentinels that feed the React runtime rendering path.
166
+
167
+ Source: maintainer interview
168
+
169
+ ---
170
+
171
+ ### HIGH: Rendering policy components without `<OpenPolicy>` provider
172
+
173
+ Wrong:
174
+ ```tsx
175
+ // privacy-page.tsx
176
+ import { PrivacyPolicy } from "@openpolicy/react";
177
+
178
+ export default function PrivacyPage() {
179
+ return <PrivacyPolicy />;
180
+ }
181
+ ```
182
+
183
+ Correct:
184
+ ```tsx
185
+ // layout.tsx — wrap at the root
186
+ import { OpenPolicy } from "@openpolicy/react";
187
+ import config from "./openpolicy";
188
+
189
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
190
+ return <OpenPolicy config={config}>{children}</OpenPolicy>;
191
+ }
192
+
193
+ // privacy-page.tsx — component reads from context
194
+ import { PrivacyPolicy } from "@openpolicy/react";
195
+
196
+ export default function PrivacyPage() {
197
+ return <PrivacyPolicy />;
198
+ }
199
+ ```
200
+
201
+ `PrivacyPolicy`, `TermsOfService`, and `CookiePolicy` read config from React context; without the provider they silently render `null` with no visible error.
202
+
203
+ Source: packages/react/src/context.tsx
204
+
205
+ ---
206
+
207
+ ### HIGH: Not spreading `dataCollected` and `thirdParties` sentinels into config
208
+
209
+ Wrong:
210
+ ```ts
211
+ // openpolicy.ts
212
+ import { defineConfig } from "@openpolicy/sdk";
213
+
214
+ export default defineConfig({
215
+ company: {
216
+ name: "Acme",
217
+ legalName: "Acme, Inc.",
218
+ address: "123 Main St, San Francisco, CA 94105",
219
+ contact: "privacy@acme.com",
220
+ },
221
+ privacy: {
222
+ effectiveDate: "2026-01-01",
223
+ dataCollected: { "Account Information": ["Email address"] },
224
+ thirdParties: [],
225
+ },
226
+ });
227
+ ```
228
+
229
+ Correct:
230
+ ```ts
231
+ // openpolicy.ts
232
+ import { defineConfig, dataCollected, thirdParties } from "@openpolicy/sdk";
233
+
234
+ export default defineConfig({
235
+ company: {
236
+ name: "Acme",
237
+ legalName: "Acme, Inc.",
238
+ address: "123 Main St, San Francisco, CA 94105",
239
+ contact: "privacy@acme.com",
240
+ },
241
+ privacy: {
242
+ effectiveDate: "2026-01-01",
243
+ dataCollected: { ...dataCollected, "Account Information": ["Email address"] },
244
+ thirdParties: [...thirdParties],
245
+ },
246
+ });
247
+ ```
248
+
249
+ Without spreading the sentinels, `autoCollect()` plugin output is discarded and the policy compiles with only the hand-declared entries — all `collecting()` and `thirdParty()` call annotations are silently ignored.
250
+
251
+ Source: packages/sdk/src/auto-collected.ts