-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathspec-changes.json
More file actions
335 lines (335 loc) · 16.7 KB
/
Copy pathspec-changes.json
File metadata and controls
335 lines (335 loc) · 16.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
{
"$comment": "GENERATED (ADR-0087 D4) — do not edit. Regenerate with: pnpm --filter @objectstack/spec gen:spec-changes. A projection of the D2 conversion table + D3 migration chain; the upgrade guide and the MCP spec_changes tool derive from this same data.",
"protocolVersion": "16.0.0",
"supportFloor": 10,
"migrateCommand": "objectstack migrate meta --from <N> (N >= 10)",
"aggregate": {
"from": 10,
"to": 16,
"added": [],
"converted": [
{
"surface": "flow.node.type",
"to": "flow callout node types 'http_request' / 'http_call' / 'webhook' → 'http'",
"conversionId": "flow-node-http-callout-rename",
"toMajor": 11
},
{
"surface": "page.kind",
"to": "page kind 'jsx' → 'html' (ADR-0080 canonical spelling)",
"conversionId": "page-kind-jsx-to-html",
"toMajor": 11
},
{
"surface": "flow.node.config.filter",
"to": "CRUD flow-node config key 'filters' → 'filter'",
"conversionId": "flow-node-crud-filter-alias",
"toMajor": 11
},
{
"surface": "object.compactLayout",
"to": "object key 'compactLayout' → 'highlightFields' (ADR-0085 semantic roles)",
"conversionId": "object-compactLayout-to-highlightFields",
"toMajor": 11
},
{
"surface": "stack.roles",
"to": "stack collection key 'roles' → 'positions' (ADR-0090 D3)",
"conversionId": "stack-roles-to-positions",
"toMajor": 13
},
{
"surface": "object.sharingModel",
"to": "object sharingModel 'read' → 'public_read', 'read_write' → 'public_read_write' (ADR-0090 D4)",
"conversionId": "owd-legacy-read-aliases",
"toMajor": 13
},
{
"surface": "sharingRule.sharedWith.type",
"to": "sharing-rule recipient type 'role' → 'position' (ADR-0090 D3)",
"conversionId": "sharing-recipient-role-to-position",
"toMajor": 13
},
{
"surface": "book.audience",
"to": "book audience gated arm '{ profile }' → '{ permissionSet }' (ADR-0090 D2/D9)",
"conversionId": "book-audience-profile-to-permission-set",
"toMajor": 14
},
{
"surface": "view.form.visibleOn",
"to": "view form section/field key 'visibleOn' → 'visibleWhen' (ADR-0089)",
"conversionId": "view-visibleOn-to-visibleWhen",
"toMajor": 15
},
{
"surface": "page.component.visibility",
"to": "page component key 'visibility' → 'visibleWhen' (ADR-0089)",
"conversionId": "page-component-visibility-to-visibleWhen",
"toMajor": 15
}
],
"migrated": [
{
"surface": "object.titleFormat",
"replacement": "object.nameField",
"migrationId": "object-titleFormat-to-nameField",
"toMajor": 11,
"rationale": "A single-field `titleFormat` maps 1:1 to `nameField`, but a composite template (e.g. `{firstName} {lastName}`) has no lossless single-field target — it must become a formula field designated as `nameField`. The choice of formula is a judgment the transform cannot make."
},
{
"surface": "security.rls.predicate",
"replacement": "CEL predicate",
"migrationId": "rls-sql-predicate-to-cel",
"toMajor": 11,
"rationale": "SQL-ish RLS predicates were deprecated in favor of canonical CEL. Translation is not a pure token rename — operators, functions, and null semantics differ — so it cannot be applied losslessly by the chain."
},
{
"surface": "api.requireAuth",
"replacement": "explicit `api: { requireAuth: false }` (intentionally-public deployments only)",
"migrationId": "rest-requireauth-default-flip",
"toMajor": 12,
"rationale": "The global default flipped from `false` to `true` in protocol 12: anonymous requests to the `/data/*` CRUD and batch endpoints are rejected with 401 unless the stack opts out. Whether anonymous access was intentional (demo / kiosk) or an accident is a security judgment no transform can make."
},
{
"surface": "permissionSet.kind / permissionSet.isProfile",
"replacement": "position-based assignment + permission-set grants (ADR-0090 D2)",
"migrationId": "permission-set-profile-removed",
"toMajor": 13,
"rationale": "The Profile concept was removed: `isProfile` is gone from `PermissionSetSchema` and the `profile` metadata kind folded into `position`. Mapping a profile onto positions and permission-set grants is an authorization-design decision, not a rename."
},
{
"surface": "position.parent / sharingRule recipient role_and_subordinates",
"replacement": "business-unit tree + `unit_and_subordinates` (ADR-0090 D3)",
"migrationId": "position-hierarchy-flattened",
"toMajor": 13,
"rationale": "Positions are flat in v2 — `parent` was removed and the `role_and_subordinates` recipient with it; hierarchy lives on the business-unit tree, which expands a DIFFERENT structure than the retired role tree. Re-homing an org hierarchy is a judgment call."
},
{
"surface": "CEL/formula: current_user.roles",
"replacement": "current_user.positions",
"migrationId": "cel-current-user-roles-to-positions",
"toMajor": 13,
"rationale": "The EvalUser/CEL contract renamed `current_user.roles` to `current_user.positions`. The token lives inside free-form expression strings, where a blind textual substitution could corrupt string literals or comments — so the rewrite is delegated to the author."
},
{
"surface": "object.sharingModel: 'full'",
"replacement": "'public_read_write' or explicit sharing rules",
"migrationId": "owd-full-alias-removed",
"toMajor": 13,
"rationale": "The legacy `'full'` OWD alias implied full access (including transfer/ delete) — wider than any canonical OWD value, so it has no lossless target ('read'/'read_write' converted mechanically; this one did not). Choosing between `public_read_write` and explicit sharing rules is a security-posture decision."
},
{
"surface": "object.sharingModel (absent, custom object with owner field)",
"replacement": "an explicit `sharingModel` declaration",
"migrationId": "sharing-model-secure-default",
"toMajor": 13,
"rationale": "ADR-0090 D1 secure default: a custom object with an owner field and NO `sharingModel` now resolves `private` (it used to fall through to fully public). Restoring the old exposure must be a deliberate, visible declaration — the chain must not silently re-open data."
},
{
"surface": "view form fields/sections · page components (undeclared keys)",
"replacement": "declared keys only (`visibleWhen` for visibility predicates)",
"migrationId": "ui-schemas-strict-unknown-keys",
"toMajor": 15,
"rationale": "The `.strict()` flip (ADR-0089 D3a) turns a previously silently-stripped unknown key into a parse error. There is no mapping target for an arbitrary unknown key — auto-deleting it would be exactly the silent data loss ADR-0078 bans — so each occurrence needs the author to decide: fix the typo, move it to the right layer, or delete dead metadata."
},
{
"surface": "dashboard widgets (undeclared top-level keys — legacy inline analytics, objectui-internal `component`/`data`, or typos)",
"replacement": "declared keys only (`dataset` + `dimensions` + `values` for analytics; `options` for renderer-specific extras)",
"migrationId": "dashboard-widget-strict-unknown-keys",
"toMajor": 16,
"rationale": "The `.strict()` flip turns a previously silently-stripped unknown key into a parse error. There is no mapping target for an arbitrary unknown key — auto-deleting it would be exactly the silent data loss ADR-0078 bans — so each occurrence needs the author to decide: bind a `dataset` and select `dimensions`/`values`, move a renderer setting under `options`, or delete the dead key."
}
],
"removed": []
},
"perMajor": [
{
"from": 10,
"to": 11,
"added": [],
"converted": [
{
"surface": "flow.node.type",
"to": "flow callout node types 'http_request' / 'http_call' / 'webhook' → 'http'",
"conversionId": "flow-node-http-callout-rename",
"toMajor": 11
},
{
"surface": "page.kind",
"to": "page kind 'jsx' → 'html' (ADR-0080 canonical spelling)",
"conversionId": "page-kind-jsx-to-html",
"toMajor": 11
},
{
"surface": "flow.node.config.filter",
"to": "CRUD flow-node config key 'filters' → 'filter'",
"conversionId": "flow-node-crud-filter-alias",
"toMajor": 11
},
{
"surface": "object.compactLayout",
"to": "object key 'compactLayout' → 'highlightFields' (ADR-0085 semantic roles)",
"conversionId": "object-compactLayout-to-highlightFields",
"toMajor": 11
}
],
"migrated": [
{
"surface": "object.titleFormat",
"replacement": "object.nameField",
"migrationId": "object-titleFormat-to-nameField",
"toMajor": 11,
"rationale": "A single-field `titleFormat` maps 1:1 to `nameField`, but a composite template (e.g. `{firstName} {lastName}`) has no lossless single-field target — it must become a formula field designated as `nameField`. The choice of formula is a judgment the transform cannot make."
},
{
"surface": "security.rls.predicate",
"replacement": "CEL predicate",
"migrationId": "rls-sql-predicate-to-cel",
"toMajor": 11,
"rationale": "SQL-ish RLS predicates were deprecated in favor of canonical CEL. Translation is not a pure token rename — operators, functions, and null semantics differ — so it cannot be applied losslessly by the chain."
}
],
"removed": []
},
{
"from": 11,
"to": 12,
"added": [],
"converted": [],
"migrated": [
{
"surface": "api.requireAuth",
"replacement": "explicit `api: { requireAuth: false }` (intentionally-public deployments only)",
"migrationId": "rest-requireauth-default-flip",
"toMajor": 12,
"rationale": "The global default flipped from `false` to `true` in protocol 12: anonymous requests to the `/data/*` CRUD and batch endpoints are rejected with 401 unless the stack opts out. Whether anonymous access was intentional (demo / kiosk) or an accident is a security judgment no transform can make."
}
],
"removed": []
},
{
"from": 12,
"to": 13,
"added": [],
"converted": [
{
"surface": "stack.roles",
"to": "stack collection key 'roles' → 'positions' (ADR-0090 D3)",
"conversionId": "stack-roles-to-positions",
"toMajor": 13
},
{
"surface": "object.sharingModel",
"to": "object sharingModel 'read' → 'public_read', 'read_write' → 'public_read_write' (ADR-0090 D4)",
"conversionId": "owd-legacy-read-aliases",
"toMajor": 13
},
{
"surface": "sharingRule.sharedWith.type",
"to": "sharing-rule recipient type 'role' → 'position' (ADR-0090 D3)",
"conversionId": "sharing-recipient-role-to-position",
"toMajor": 13
}
],
"migrated": [
{
"surface": "permissionSet.kind / permissionSet.isProfile",
"replacement": "position-based assignment + permission-set grants (ADR-0090 D2)",
"migrationId": "permission-set-profile-removed",
"toMajor": 13,
"rationale": "The Profile concept was removed: `isProfile` is gone from `PermissionSetSchema` and the `profile` metadata kind folded into `position`. Mapping a profile onto positions and permission-set grants is an authorization-design decision, not a rename."
},
{
"surface": "position.parent / sharingRule recipient role_and_subordinates",
"replacement": "business-unit tree + `unit_and_subordinates` (ADR-0090 D3)",
"migrationId": "position-hierarchy-flattened",
"toMajor": 13,
"rationale": "Positions are flat in v2 — `parent` was removed and the `role_and_subordinates` recipient with it; hierarchy lives on the business-unit tree, which expands a DIFFERENT structure than the retired role tree. Re-homing an org hierarchy is a judgment call."
},
{
"surface": "CEL/formula: current_user.roles",
"replacement": "current_user.positions",
"migrationId": "cel-current-user-roles-to-positions",
"toMajor": 13,
"rationale": "The EvalUser/CEL contract renamed `current_user.roles` to `current_user.positions`. The token lives inside free-form expression strings, where a blind textual substitution could corrupt string literals or comments — so the rewrite is delegated to the author."
},
{
"surface": "object.sharingModel: 'full'",
"replacement": "'public_read_write' or explicit sharing rules",
"migrationId": "owd-full-alias-removed",
"toMajor": 13,
"rationale": "The legacy `'full'` OWD alias implied full access (including transfer/ delete) — wider than any canonical OWD value, so it has no lossless target ('read'/'read_write' converted mechanically; this one did not). Choosing between `public_read_write` and explicit sharing rules is a security-posture decision."
},
{
"surface": "object.sharingModel (absent, custom object with owner field)",
"replacement": "an explicit `sharingModel` declaration",
"migrationId": "sharing-model-secure-default",
"toMajor": 13,
"rationale": "ADR-0090 D1 secure default: a custom object with an owner field and NO `sharingModel` now resolves `private` (it used to fall through to fully public). Restoring the old exposure must be a deliberate, visible declaration — the chain must not silently re-open data."
}
],
"removed": []
},
{
"from": 13,
"to": 14,
"added": [],
"converted": [
{
"surface": "book.audience",
"to": "book audience gated arm '{ profile }' → '{ permissionSet }' (ADR-0090 D2/D9)",
"conversionId": "book-audience-profile-to-permission-set",
"toMajor": 14
}
],
"migrated": [],
"removed": []
},
{
"from": 14,
"to": 15,
"added": [],
"converted": [
{
"surface": "view.form.visibleOn",
"to": "view form section/field key 'visibleOn' → 'visibleWhen' (ADR-0089)",
"conversionId": "view-visibleOn-to-visibleWhen",
"toMajor": 15
},
{
"surface": "page.component.visibility",
"to": "page component key 'visibility' → 'visibleWhen' (ADR-0089)",
"conversionId": "page-component-visibility-to-visibleWhen",
"toMajor": 15
}
],
"migrated": [
{
"surface": "view form fields/sections · page components (undeclared keys)",
"replacement": "declared keys only (`visibleWhen` for visibility predicates)",
"migrationId": "ui-schemas-strict-unknown-keys",
"toMajor": 15,
"rationale": "The `.strict()` flip (ADR-0089 D3a) turns a previously silently-stripped unknown key into a parse error. There is no mapping target for an arbitrary unknown key — auto-deleting it would be exactly the silent data loss ADR-0078 bans — so each occurrence needs the author to decide: fix the typo, move it to the right layer, or delete dead metadata."
}
],
"removed": []
},
{
"from": 15,
"to": 16,
"added": [],
"converted": [],
"migrated": [
{
"surface": "dashboard widgets (undeclared top-level keys — legacy inline analytics, objectui-internal `component`/`data`, or typos)",
"replacement": "declared keys only (`dataset` + `dimensions` + `values` for analytics; `options` for renderer-specific extras)",
"migrationId": "dashboard-widget-strict-unknown-keys",
"toMajor": 16,
"rationale": "The `.strict()` flip turns a previously silently-stripped unknown key into a parse error. There is no mapping target for an arbitrary unknown key — auto-deleting it would be exactly the silent data loss ADR-0078 bans — so each occurrence needs the author to decide: bind a `dataset` and select `dimensions`/`values`, move a renderer setting under `options`, or delete the dead key."
}
],
"removed": []
}
]
}