@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,360 @@
1
+ ---
2
+ name: migrate
3
+ description: >
4
+ Converting existing hand-written privacy policies or terms of service documents into OpenPolicy defineConfig() configs — mapping prose sections to structured TypeScript fields without passing raw text as values.
5
+ type: lifecycle
6
+ library: openpolicy
7
+ library_version: "0.0.19"
8
+ requires:
9
+ - openpolicy/define-config
10
+ sources:
11
+ - jamiedavenport/openpolicy:packages/core/src/types.ts
12
+ - jamiedavenport/openpolicy:packages/sdk/src/constants.ts
13
+ ---
14
+
15
+ # openpolicy/migrate
16
+
17
+ This skill builds on openpolicy/define-config. Read it first.
18
+
19
+ OpenPolicy generates all prose from structured fields. The job of a migration is to extract structure from an existing document — not to transcribe its sentences. Every field value must be a short label, boolean, enum string, or object; never a paragraph.
20
+
21
+ ## Setup: Before and After
22
+
23
+ ### Existing privacy policy (excerpt)
24
+
25
+ ```
26
+ Privacy Policy — Effective January 1, 2026
27
+
28
+ Acme, Inc. ("Acme") operates the Acme platform. This policy describes how we collect
29
+ and use personal data in compliance with the GDPR and the California Consumer Privacy Act.
30
+
31
+ Data We Collect
32
+ We collect the following categories of personal data:
33
+ - Account information: your name, email address, and password when you register.
34
+ - Payment details: the last 4 digits of your card, your billing name, and billing address.
35
+ - Usage data: pages you visit, features you use, and time spent in the app.
36
+ - Device information: your device type, operating system, and browser version.
37
+
38
+ We use Google Analytics for product analytics and Stripe for payment processing.
39
+
40
+ Legal Basis (GDPR)
41
+ We process your data on the basis of legitimate interests and, where required, consent.
42
+
43
+ Data Retention
44
+ Account information is held until you delete your account. Usage data is retained for
45
+ 90 days. Payment records are kept for 3 years as required by applicable law.
46
+
47
+ Your Rights
48
+ Under the GDPR you have the right to access, correct, erase, port, restrict, and object
49
+ to processing of your data. California residents may opt out of the sale of personal
50
+ information and are protected from discrimination for exercising their rights.
51
+
52
+ Contact: privacy@acme.com | Acme, Inc., 123 Main St, San Francisco, CA 94105
53
+ ```
54
+
55
+ ### Migrated config
56
+
57
+ ```ts
58
+ // openpolicy.ts
59
+ import {
60
+ defineConfig,
61
+ Compliance,
62
+ DataCategories,
63
+ Retention,
64
+ Providers,
65
+ dataCollected,
66
+ thirdParties,
67
+ } from "@openpolicy/sdk";
68
+
69
+ export default defineConfig({
70
+ company: {
71
+ name: "Acme",
72
+ legalName: "Acme, Inc.",
73
+ address: "123 Main St, San Francisco, CA 94105",
74
+ contact: "privacy@acme.com",
75
+ },
76
+ privacy: {
77
+ effectiveDate: "2026-01-01",
78
+ // GDPR + CCPA: spread both presets, then union the array fields
79
+ ...Compliance.GDPR,
80
+ jurisdictions: [
81
+ ...Compliance.GDPR.jurisdictions,
82
+ ...Compliance.CCPA.jurisdictions,
83
+ ],
84
+ userRights: [
85
+ ...Compliance.GDPR.userRights,
86
+ ...Compliance.CCPA.userRights,
87
+ ],
88
+ dataCollected: {
89
+ ...dataCollected,
90
+ ...DataCategories.AccountInfo,
91
+ ...DataCategories.PaymentInfo,
92
+ ...DataCategories.UsageData,
93
+ ...DataCategories.DeviceInfo,
94
+ },
95
+ retention: {
96
+ "Account Information": Retention.UntilAccountDeletion,
97
+ "Usage Data": Retention.NinetyDays,
98
+ "Payment Information": Retention.ThreeYears,
99
+ },
100
+ cookies: { essential: true, analytics: true, marketing: false },
101
+ thirdParties: [
102
+ ...thirdParties,
103
+ Providers.GoogleAnalytics,
104
+ Providers.Stripe,
105
+ ],
106
+ },
107
+ });
108
+ ```
109
+
110
+ ## Core Patterns
111
+
112
+ ### 1. Mapping data collection sections
113
+
114
+ Read each "data we collect" section and identify the category name and the specific fields listed. Map each to a short label — never copy sentences.
115
+
116
+ `DataCategories` presets cover the most common categories. Check if the existing policy's categories match before reaching for custom keys:
117
+
118
+ | Preset | Generated key | Fields |
119
+ |---|---|---|
120
+ | `DataCategories.AccountInfo` | `"Account Information"` | Name, Email address |
121
+ | `DataCategories.SessionData` | `"Session Data"` | IP address, User agent, Browser type |
122
+ | `DataCategories.PaymentInfo` | `"Payment Information"` | Card last 4 digits, Billing name, Billing address |
123
+ | `DataCategories.UsageData` | `"Usage Data"` | Pages visited, Features used, Time spent |
124
+ | `DataCategories.DeviceInfo` | `"Device Information"` | Device type, Operating system, Browser version |
125
+ | `DataCategories.LocationData` | `"Location Data"` | Country, City, Timezone |
126
+ | `DataCategories.Communications` | `"Communications"` | Email content, Support tickets |
127
+
128
+ For categories not covered by a preset, add a custom key with short field labels:
129
+
130
+ ```ts
131
+ dataCollected: {
132
+ ...dataCollected,
133
+ ...DataCategories.AccountInfo,
134
+ "Health Data": ["Blood glucose readings", "Heart rate"],
135
+ },
136
+ ```
137
+
138
+ Always spread `dataCollected` first so the autoCollect plugin's output is included alongside the explicit entries.
139
+
140
+ ### 2. Mapping jurisdiction and legal basis from GDPR/CCPA language
141
+
142
+ Scan the existing policy for jurisdiction signals:
143
+
144
+ | Prose signal | Maps to |
145
+ |---|---|
146
+ | "GDPR", "EU", "EEA", "European" | `jurisdictions: ["eu"]` |
147
+ | "CCPA", "California", "California residents" | `jurisdictions: ["ca"]` |
148
+ | "Australian Privacy Act" | `jurisdictions: ["au"]` |
149
+ | No specific regulation cited | `jurisdictions: ["us"]` |
150
+
151
+ For legal basis (GDPR policies only), map the stated basis:
152
+
153
+ | Prose | `legalBasis` value |
154
+ |---|---|
155
+ | "legitimate interests" | `"legitimate_interests"` |
156
+ | "your consent" / "you have agreed" | `"consent"` |
157
+ | "to perform a contract" / "to provide the service" | `"contract"` |
158
+ | "legal obligation" / "required by law" | `"legal_obligation"` |
159
+
160
+ When the policy states more than one basis, use an array:
161
+
162
+ ```ts
163
+ legalBasis: ["legitimate_interests", "consent"],
164
+ ```
165
+
166
+ Use `Compliance.GDPR` or `Compliance.CCPA` as a starting point when the existing policy explicitly targets those regulations. Merge the array fields when both apply:
167
+
168
+ ```ts
169
+ ...Compliance.GDPR,
170
+ jurisdictions: [...Compliance.GDPR.jurisdictions, ...Compliance.CCPA.jurisdictions],
171
+ userRights: [...Compliance.GDPR.userRights, ...Compliance.CCPA.userRights],
172
+ ```
173
+
174
+ `Compliance.CCPA` does not include `legalBasis` — only add it when the existing policy states an EU legal basis.
175
+
176
+ ### 3. Mapping TermsOfService optional sections
177
+
178
+ `TermsOfServiceConfig` has three required fields (`effectiveDate`, `acceptance`, `governingLaw`) and many optional ones. The rule for migration: if the existing document has a section covering a concept, include the corresponding config field.
179
+
180
+ Read each section heading and match it:
181
+
182
+ | Existing section | Config field |
183
+ |---|---|
184
+ | Age / eligibility requirements | `eligibility` |
185
+ | Account creation, credentials | `accounts` |
186
+ | Prohibited conduct / acceptable use | `prohibitedUses` |
187
+ | User-generated content, uploads | `userContent` |
188
+ | Intellectual property, trademarks | `intellectualProperty` |
189
+ | Pricing, subscriptions, refunds | `payments` |
190
+ | Uptime, maintenance | `availability` |
191
+ | Account suspension / cancellation | `termination` |
192
+ | Disclaimer of warranties | `disclaimers` |
193
+ | Limitation of liability | `limitationOfLiability` |
194
+ | Indemnification | `indemnification` |
195
+ | Third-party services / links | `thirdPartyServices` |
196
+ | Dispute resolution, arbitration | `disputeResolution` |
197
+ | How changes are communicated | `changesPolicy` |
198
+ | Link to privacy policy | `privacyPolicyUrl` |
199
+
200
+ A full migration for a SaaS product with payments, user content, and arbitration:
201
+
202
+ ```ts
203
+ terms: {
204
+ effectiveDate: "2026-01-01",
205
+ acceptance: { methods: ["creating an account", "using the service"] },
206
+ governingLaw: { jurisdiction: "Delaware, USA" },
207
+ eligibility: { minimumAge: 18 },
208
+ accounts: {
209
+ registrationRequired: true,
210
+ userResponsibleForCredentials: true,
211
+ companyCanTerminate: true,
212
+ },
213
+ prohibitedUses: [
214
+ "Reverse engineering the service",
215
+ "Automated scraping without prior written consent",
216
+ "Using the service to violate applicable law",
217
+ ],
218
+ userContent: {
219
+ usersOwnContent: true,
220
+ licenseGrantedToCompany: true,
221
+ licenseDescription: "A worldwide, royalty-free license to host and display your content.",
222
+ companyCanRemoveContent: true,
223
+ },
224
+ payments: {
225
+ hasPaidFeatures: true,
226
+ refundPolicy: "30-day money-back guarantee on annual plans.",
227
+ priceChangesNotice: "30 days advance notice via email.",
228
+ },
229
+ termination: {
230
+ companyCanTerminate: true,
231
+ userCanTerminate: true,
232
+ effectOfTermination: "All licenses granted to the user terminate immediately.",
233
+ },
234
+ disclaimers: { serviceProvidedAsIs: true, noWarranties: true },
235
+ limitationOfLiability: {
236
+ excludesIndirectDamages: true,
237
+ liabilityCap: "Fees paid in the prior 12 months.",
238
+ },
239
+ disputeResolution: {
240
+ method: "arbitration",
241
+ venue: "San Francisco, CA",
242
+ classActionWaiver: true,
243
+ },
244
+ changesPolicy: {
245
+ noticeMethod: "Email notification",
246
+ noticePeriodDays: 30,
247
+ },
248
+ privacyPolicyUrl: "https://acme.com/privacy",
249
+ },
250
+ ```
251
+
252
+ ### 4. Using presets to standardize values
253
+
254
+ Prefer preset constants over raw strings wherever the meaning matches exactly. This reduces typo risk and keeps the config readable.
255
+
256
+ **Retention periods** — match common prose to preset keys:
257
+
258
+ | Prose | Preset |
259
+ |---|---|
260
+ | "until you delete your account" | `Retention.UntilAccountDeletion` |
261
+ | "until your session ends" | `Retention.UntilSessionExpiry` |
262
+ | "30 days" | `Retention.ThirtyDays` |
263
+ | "90 days" | `Retention.NinetyDays` |
264
+ | "1 year" | `Retention.OneYear` |
265
+ | "3 years" | `Retention.ThreeYears` |
266
+ | "as required by law" | `Retention.AsRequiredByLaw` |
267
+
268
+ For a period not in the preset list, use a plain string:
269
+
270
+ ```ts
271
+ retention: {
272
+ "Audit Logs": "7 years",
273
+ },
274
+ ```
275
+
276
+ **User rights** — `UserRight` enum values and their prose equivalents:
277
+
278
+ | Prose | Value |
279
+ |---|---|
280
+ | right of access / to view your data | `"access"` |
281
+ | right to correct / rectify | `"rectification"` |
282
+ | right to delete / erasure / "right to be forgotten" | `"erasure"` |
283
+ | right to data portability | `"portability"` |
284
+ | right to restrict processing | `"restriction"` |
285
+ | right to object | `"objection"` |
286
+ | right to opt out of sale | `"opt_out_sale"` |
287
+ | right to non-discrimination | `"non_discrimination"` |
288
+
289
+ ## Common Mistakes
290
+
291
+ ### HIGH — Passing prose text as field values instead of mapping to structured fields
292
+
293
+ OpenPolicy generates all human-readable sentences from the config structure. Passing paragraph text into fields produces malformed or legally duplicated output.
294
+
295
+ Wrong:
296
+ ```ts
297
+ privacy: {
298
+ dataCollected: {
299
+ // WRONG: prose sentence passed as a field label
300
+ "Data": ["We collect information you provide when you register for an account, including your name and email address."],
301
+ },
302
+ }
303
+ ```
304
+
305
+ Correct:
306
+ ```ts
307
+ privacy: {
308
+ dataCollected: {
309
+ "Account Information": ["Name", "Email address"],
310
+ },
311
+ }
312
+ ```
313
+
314
+ The same principle applies to `prohibitedUses` (keep each entry to a short clause, not a full paragraph), `acceptance.methods` (short action phrases, not sentences), and `payments.refundPolicy` (one concise sentence is acceptable; not a multi-paragraph refund terms block).
315
+
316
+ Source: `packages/core/src/types.ts`
317
+
318
+ ---
319
+
320
+ ### MEDIUM — Omitting optional TermsOfService sections that cover existing policy content
321
+
322
+ Skipping optional sections when the existing document has matching content produces a terms document that is legally less complete than what the team approved.
323
+
324
+ Wrong:
325
+ ```ts
326
+ // Existing policy has payment terms, user content policy, and account termination section —
327
+ // but all three are omitted from the config.
328
+ terms: {
329
+ effectiveDate: "2026-01-01",
330
+ acceptance: { methods: ["using the service"] },
331
+ governingLaw: { jurisdiction: "Delaware, USA" },
332
+ }
333
+ ```
334
+
335
+ Correct:
336
+ ```ts
337
+ terms: {
338
+ effectiveDate: "2026-01-01",
339
+ acceptance: { methods: ["using the service"] },
340
+ governingLaw: { jurisdiction: "Delaware, USA" },
341
+ payments: {
342
+ hasPaidFeatures: true,
343
+ refundPolicy: "30-day money-back guarantee.",
344
+ priceChangesNotice: "30 days advance notice.",
345
+ },
346
+ userContent: {
347
+ usersOwnContent: true,
348
+ licenseGrantedToCompany: true,
349
+ companyCanRemoveContent: true,
350
+ },
351
+ termination: {
352
+ companyCanTerminate: true,
353
+ userCanTerminate: true,
354
+ },
355
+ }
356
+ ```
357
+
358
+ Before finalizing the config, re-read each section of the original document and confirm a corresponding field is present in the output config. Optional sections that are absent from the config are entirely omitted from the compiled document — there is no fallback prose.
359
+
360
+ Source: `packages/core/src/types.ts`