container-superposition 0.1.1 → 0.1.3

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.
Files changed (136) hide show
  1. package/README.md +206 -1
  2. package/dist/scripts/init.js +235 -179
  3. package/dist/scripts/init.js.map +1 -1
  4. package/dist/tool/commands/doctor.d.ts +15 -0
  5. package/dist/tool/commands/doctor.d.ts.map +1 -0
  6. package/dist/tool/commands/doctor.js +862 -0
  7. package/dist/tool/commands/doctor.js.map +1 -0
  8. package/dist/tool/commands/explain.d.ts +13 -0
  9. package/dist/tool/commands/explain.d.ts.map +1 -0
  10. package/dist/tool/commands/explain.js +211 -0
  11. package/dist/tool/commands/explain.js.map +1 -0
  12. package/dist/tool/commands/list.d.ts +16 -0
  13. package/dist/tool/commands/list.d.ts.map +1 -0
  14. package/dist/tool/commands/list.js +121 -0
  15. package/dist/tool/commands/list.js.map +1 -0
  16. package/dist/tool/commands/plan.d.ts +16 -0
  17. package/dist/tool/commands/plan.d.ts.map +1 -0
  18. package/dist/tool/commands/plan.js +329 -0
  19. package/dist/tool/commands/plan.js.map +1 -0
  20. package/dist/tool/questionnaire/composer.d.ts +6 -1
  21. package/dist/tool/questionnaire/composer.d.ts.map +1 -1
  22. package/dist/tool/questionnaire/composer.js +300 -202
  23. package/dist/tool/questionnaire/composer.js.map +1 -1
  24. package/dist/tool/readme/markdown-parser.d.ts.map +1 -1
  25. package/dist/tool/readme/markdown-parser.js.map +1 -1
  26. package/dist/tool/readme/readme-generator.d.ts.map +1 -1
  27. package/dist/tool/readme/readme-generator.js +11 -6
  28. package/dist/tool/readme/readme-generator.js.map +1 -1
  29. package/dist/tool/schema/deployment-targets.d.ts +77 -0
  30. package/dist/tool/schema/deployment-targets.d.ts.map +1 -0
  31. package/dist/tool/schema/deployment-targets.js +91 -0
  32. package/dist/tool/schema/deployment-targets.js.map +1 -0
  33. package/dist/tool/schema/manifest-migrations.d.ts +51 -0
  34. package/dist/tool/schema/manifest-migrations.d.ts.map +1 -0
  35. package/dist/tool/schema/manifest-migrations.js +159 -0
  36. package/dist/tool/schema/manifest-migrations.js.map +1 -0
  37. package/dist/tool/schema/overlay-loader.d.ts +1 -1
  38. package/dist/tool/schema/overlay-loader.d.ts.map +1 -1
  39. package/dist/tool/schema/overlay-loader.js +42 -14
  40. package/dist/tool/schema/overlay-loader.js.map +1 -1
  41. package/dist/tool/schema/types.d.ts +44 -2
  42. package/dist/tool/schema/types.d.ts.map +1 -1
  43. package/dist/tool/utils/merge.d.ts +134 -0
  44. package/dist/tool/utils/merge.d.ts.map +1 -0
  45. package/dist/tool/utils/merge.js +277 -0
  46. package/dist/tool/utils/merge.js.map +1 -0
  47. package/dist/tool/utils/port-utils.d.ts +29 -0
  48. package/dist/tool/utils/port-utils.d.ts.map +1 -0
  49. package/dist/tool/utils/port-utils.js +128 -0
  50. package/dist/tool/utils/port-utils.js.map +1 -0
  51. package/dist/tool/utils/version.d.ts +9 -0
  52. package/dist/tool/utils/version.d.ts.map +1 -0
  53. package/dist/tool/utils/version.js +32 -0
  54. package/dist/tool/utils/version.js.map +1 -0
  55. package/docs/architecture.md +25 -21
  56. package/docs/deployment-targets.md +150 -0
  57. package/docs/discovery-commands.md +442 -0
  58. package/docs/merge-strategy.md +700 -0
  59. package/docs/minimal-and-editor.md +265 -0
  60. package/docs/overlay-imports.md +209 -0
  61. package/docs/overlay-manifest-refactoring.md +2 -2
  62. package/docs/overlay-metadata-archive.md +1 -1
  63. package/docs/overlays.md +91 -23
  64. package/docs/presets-architecture.md +3 -3
  65. package/docs/presets.md +1 -1
  66. package/docs/publishing.md +36 -35
  67. package/docs/team-workflow.md +540 -0
  68. package/overlays/.presets/data-engineering.yml +392 -0
  69. package/overlays/.presets/event-sourced-service.yml +262 -0
  70. package/overlays/.presets/frontend.yml +287 -0
  71. package/overlays/.presets/k8s-operator-dev.yml +462 -0
  72. package/overlays/.registry/README.md +1 -1
  73. package/overlays/.registry/deployment-targets.yml +54 -0
  74. package/overlays/.shared/README.md +43 -0
  75. package/overlays/.shared/compose/common-healthchecks.yml +38 -0
  76. package/overlays/.shared/otel/instrumentation.env +20 -0
  77. package/overlays/.shared/otel/otel-base-config.yaml +30 -0
  78. package/overlays/.shared/vscode/recommended-extensions.json +14 -0
  79. package/overlays/README.md +1 -1
  80. package/overlays/codex/overlay.yml +1 -0
  81. package/overlays/duckdb/README.md +274 -0
  82. package/overlays/duckdb/devcontainer.patch.json +10 -0
  83. package/overlays/duckdb/overlay.yml +17 -0
  84. package/overlays/duckdb/setup.sh +45 -0
  85. package/overlays/duckdb/verify.sh +32 -0
  86. package/overlays/git-helpers/overlay.yml +1 -0
  87. package/overlays/grafana/README.md +5 -5
  88. package/overlays/grafana/dashboard-provider.yml +1 -1
  89. package/overlays/grafana/docker-compose.yml +2 -2
  90. package/overlays/grafana/overlay.yml +6 -1
  91. package/overlays/jaeger/overlay.yml +16 -3
  92. package/overlays/jupyter/.env.example +6 -0
  93. package/overlays/jupyter/README.md +210 -0
  94. package/overlays/jupyter/devcontainer.patch.json +14 -0
  95. package/overlays/jupyter/docker-compose.yml +23 -0
  96. package/overlays/jupyter/overlay.yml +18 -0
  97. package/overlays/jupyter/verify.sh +35 -0
  98. package/overlays/kind/README.md +221 -0
  99. package/overlays/kind/devcontainer.patch.json +10 -0
  100. package/overlays/kind/overlay.yml +18 -0
  101. package/overlays/kind/setup.sh +43 -0
  102. package/overlays/kind/verify.sh +40 -0
  103. package/overlays/localstack/.env.example +6 -0
  104. package/overlays/localstack/README.md +188 -0
  105. package/overlays/localstack/devcontainer.patch.json +21 -0
  106. package/overlays/localstack/docker-compose.yml +25 -0
  107. package/overlays/localstack/overlay.yml +18 -0
  108. package/overlays/localstack/verify.sh +47 -0
  109. package/overlays/loki/overlay.yml +6 -1
  110. package/overlays/modern-cli-tools/overlay.yml +1 -0
  111. package/overlays/mongodb/overlay.yml +12 -2
  112. package/overlays/mysql/overlay.yml +12 -2
  113. package/overlays/nats/overlay.yml +12 -2
  114. package/overlays/openapi-tools/README.md +243 -0
  115. package/overlays/openapi-tools/devcontainer.patch.json +10 -0
  116. package/overlays/openapi-tools/overlay.yml +16 -0
  117. package/overlays/openapi-tools/setup.sh +45 -0
  118. package/overlays/openapi-tools/verify.sh +51 -0
  119. package/overlays/otel-collector/overlay.yml.example +26 -0
  120. package/overlays/postgres/overlay.yml +6 -1
  121. package/overlays/prometheus/overlay.yml +6 -1
  122. package/overlays/rabbitmq/overlay.yml +12 -2
  123. package/overlays/redis/overlay.yml +6 -1
  124. package/overlays/tilt/README.md +259 -0
  125. package/overlays/tilt/devcontainer.patch.json +17 -0
  126. package/overlays/tilt/overlay.yml +19 -0
  127. package/overlays/tilt/setup.sh +25 -0
  128. package/overlays/tilt/verify.sh +24 -0
  129. package/package.json +8 -6
  130. package/tool/README.md +12 -16
  131. package/tool/schema/overlay-manifest.schema.json +64 -4
  132. package/tool/schema/superposition-manifest.schema.json +104 -0
  133. /package/overlays/{presets → .presets}/docs-site.yml +0 -0
  134. /package/overlays/{presets → .presets}/fullstack.yml +0 -0
  135. /package/overlays/{presets → .presets}/microservice.yml +0 -0
  136. /package/overlays/{presets → .presets}/web-api.yml +0 -0
@@ -0,0 +1,700 @@
1
+ # Merge Strategy Specification
2
+
3
+ This document defines the **exact, deterministic merge behavior** for container-superposition composition. All merge operations must follow these rules to ensure predictable and reproducible results.
4
+
5
+ ## Design Principles
6
+
7
+ 1. **Deterministic**: Given the same inputs, merging always produces the same output
8
+ 2. **Explicit**: No magic or undocumented special cases
9
+ 3. **Last Writer Wins**: For conflicting primitive values, later overlays override earlier ones
10
+ 4. **Union by Default**: Arrays and collections are merged (unioned), not replaced
11
+ 5. **Deep Merge**: Objects are recursively merged, not replaced wholesale
12
+
13
+ ## File Types and Merge Strategies
14
+
15
+ ### devcontainer.json (JSON Merging)
16
+
17
+ DevContainer configurations use **deep object merging** with field-specific strategies for arrays.
18
+
19
+ #### Object Merging
20
+
21
+ **Rule**: Objects are recursively merged. When both target and source contain the same key:
22
+
23
+ - If both values are objects (non-array): recursively merge
24
+ - If both values are arrays: apply array-specific strategy (see below)
25
+ - If values are primitives: source overwrites target (last writer wins)
26
+
27
+ **Example**:
28
+
29
+ ```json
30
+ // Base
31
+ {
32
+ "customizations": {
33
+ "vscode": {
34
+ "settings": {
35
+ "editor.fontSize": 14
36
+ }
37
+ }
38
+ }
39
+ }
40
+
41
+ // Overlay
42
+ {
43
+ "customizations": {
44
+ "vscode": {
45
+ "settings": {
46
+ "editor.tabSize": 2
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ // Result (deep merged)
53
+ {
54
+ "customizations": {
55
+ "vscode": {
56
+ "settings": {
57
+ "editor.fontSize": 14,
58
+ "editor.tabSize": 2
59
+ }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ #### Array Merge Strategies
66
+
67
+ Arrays have **field-specific merge strategies** to ensure correctness:
68
+
69
+ **Union Strategy** (default for most arrays):
70
+
71
+ - Concatenate arrays
72
+ - Deduplicate elements
73
+ - Preserve order (target items first, then source items)
74
+ - Fields: `features`, `extensions`, `mounts`, `forwardPorts`, `runArgs`, `capAdd`
75
+
76
+ **Example**:
77
+
78
+ ```json
79
+ // Base
80
+ {
81
+ "forwardPorts": [3000, 8080]
82
+ }
83
+
84
+ // Overlay
85
+ {
86
+ "forwardPorts": [8080, 9090]
87
+ }
88
+
89
+ // Result (union, deduplicated)
90
+ {
91
+ "forwardPorts": [3000, 8080, 9090]
92
+ }
93
+ ```
94
+
95
+ **Features Object Merge**:
96
+
97
+ - Features are objects with keys being feature identifiers
98
+ - Feature configurations are deep merged
99
+ - If same feature appears in multiple overlays, configurations are merged
100
+
101
+ **Example**:
102
+
103
+ ```json
104
+ // Base
105
+ {
106
+ "features": {
107
+ "ghcr.io/devcontainers/features/node:1": {
108
+ "version": "lts"
109
+ }
110
+ }
111
+ }
112
+
113
+ // Overlay
114
+ {
115
+ "features": {
116
+ "ghcr.io/devcontainers/features/node:1": {
117
+ "nodeGypDependencies": true
118
+ },
119
+ "ghcr.io/devcontainers/features/git:1": {}
120
+ }
121
+ }
122
+
123
+ // Result (features merged)
124
+ {
125
+ "features": {
126
+ "ghcr.io/devcontainers/features/node:1": {
127
+ "version": "lts",
128
+ "nodeGypDependencies": true
129
+ },
130
+ "ghcr.io/devcontainers/features/git:1": {}
131
+ }
132
+ }
133
+ ```
134
+
135
+ #### Special Field: remoteEnv
136
+
137
+ **Rule**: Environment variables are merged with intelligent PATH handling.
138
+
139
+ **For PATH variables**:
140
+
141
+ 1. Split both target and source PATH on `:` (preserving `${...}` references)
142
+ 2. Filter out `${containerEnv:PATH}` placeholder from both
143
+ 3. Concatenate and deduplicate path components
144
+ 4. Append `${containerEnv:PATH}` at the end
145
+
146
+ **For other environment variables**:
147
+
148
+ - Source overwrites target (last writer wins)
149
+
150
+ **Example**:
151
+
152
+ ```json
153
+ // Base
154
+ {
155
+ "remoteEnv": {
156
+ "PATH": "/usr/local/bin:${containerEnv:PATH}",
157
+ "NODE_ENV": "development"
158
+ }
159
+ }
160
+
161
+ // Overlay
162
+ {
163
+ "remoteEnv": {
164
+ "PATH": "${containerEnv:HOME}/.local/bin:${containerEnv:PATH}",
165
+ "NODE_ENV": "production"
166
+ }
167
+ }
168
+
169
+ // Result
170
+ {
171
+ "remoteEnv": {
172
+ "PATH": "/usr/local/bin:${containerEnv:HOME}/.local/bin:${containerEnv:PATH}",
173
+ "NODE_ENV": "production"
174
+ }
175
+ }
176
+ ```
177
+
178
+ #### Special Field: portsAttributes
179
+
180
+ **Rule**: Port attributes are merged by port number key.
181
+
182
+ **Example**:
183
+
184
+ ```json
185
+ // Base
186
+ {
187
+ "portsAttributes": {
188
+ "3000": {
189
+ "label": "Dev Server"
190
+ }
191
+ }
192
+ }
193
+
194
+ // Overlay
195
+ {
196
+ "portsAttributes": {
197
+ "3000": {
198
+ "onAutoForward": "openBrowser"
199
+ },
200
+ "8080": {
201
+ "label": "API"
202
+ }
203
+ }
204
+ }
205
+
206
+ // Result (merged by port key)
207
+ {
208
+ "portsAttributes": {
209
+ "3000": {
210
+ "label": "Dev Server",
211
+ "onAutoForward": "openBrowser"
212
+ },
213
+ "8080": {
214
+ "label": "API"
215
+ }
216
+ }
217
+ }
218
+ ```
219
+
220
+ ### docker-compose.yml (YAML Merging)
221
+
222
+ Docker Compose files use **service-based deep merging**.
223
+
224
+ #### Service Merging
225
+
226
+ **Rule**: Services are merged by service name using deep object merge.
227
+
228
+ **Example**:
229
+
230
+ ```yaml
231
+ # Base
232
+ services:
233
+ devcontainer:
234
+ image: mcr.microsoft.com/devcontainers/base:ubuntu
235
+ volumes:
236
+ - ../:/workspace:cached
237
+
238
+ # Overlay
239
+ services:
240
+ devcontainer:
241
+ environment:
242
+ NODE_ENV: development
243
+ ports:
244
+ - "3000:3000"
245
+
246
+ # Result (deep merged)
247
+ services:
248
+ devcontainer:
249
+ image: mcr.microsoft.com/devcontainers/base:ubuntu
250
+ volumes:
251
+ - ../:/workspace:cached
252
+ environment:
253
+ NODE_ENV: development
254
+ ports:
255
+ - "3000:3000"
256
+ ```
257
+
258
+ #### Array Fields in Services
259
+
260
+ **Rule**: Service array fields (volumes, ports, environment, etc.) are **concatenated and deduplicated**.
261
+
262
+ **Example**:
263
+
264
+ ```yaml
265
+ # Base service
266
+ volumes:
267
+ - postgres-data:/var/lib/postgresql/data
268
+
269
+ # Overlay adds to same service
270
+ volumes:
271
+ - postgres-data:/var/lib/postgresql/data
272
+ - ./backups:/backups
273
+
274
+ # Result (union)
275
+ volumes:
276
+ - postgres-data:/var/lib/postgresql/data
277
+ - ./backups:/backups
278
+ ```
279
+
280
+ #### Special Field: depends_on
281
+
282
+ **Rule**: Filter dependencies to only include services that exist in the final composition.
283
+
284
+ **Supported syntaxes**:
285
+
286
+ - Array form: `depends_on: [serviceA, serviceB]`
287
+ - Object form: `depends_on: { serviceA: { condition: ... } }`
288
+
289
+ **Example**:
290
+
291
+ ```yaml
292
+ # Overlay defines dependency
293
+ services:
294
+ app:
295
+ depends_on:
296
+ - postgres
297
+ - redis
298
+ - rabbitmq # This service doesn't exist
299
+
300
+ # After filtering (rabbitmq removed)
301
+ services:
302
+ app:
303
+ depends_on:
304
+ - postgres
305
+ - redis
306
+ ```
307
+
308
+ #### Volumes and Networks
309
+
310
+ **Rule**: Volumes and networks are merged by name.
311
+
312
+ **Example**:
313
+
314
+ ```yaml
315
+ # Base
316
+ volumes:
317
+ postgres-data:
318
+
319
+ networks:
320
+ devnet:
321
+
322
+ # Overlay
323
+ volumes:
324
+ redis-data:
325
+
326
+ # Result (merged by key)
327
+ volumes:
328
+ postgres-data:
329
+ redis-data:
330
+
331
+ networks:
332
+ devnet:
333
+ ```
334
+
335
+ #### Port Offset
336
+
337
+ **Rule**: When port offset is specified, it's applied to **host ports only** (not container ports).
338
+
339
+ **Format**: `"host:container"` → `"(host+offset):container"`
340
+
341
+ **Example**:
342
+
343
+ ```yaml
344
+ # Before offset (offset = 100)
345
+ ports:
346
+ - "5432:5432"
347
+ - "6379:6379"
348
+
349
+ # After offset
350
+ ports:
351
+ - "5532:5432" # host port shifted
352
+ - "6479:6379" # host port shifted
353
+ ```
354
+
355
+ ### .env Files
356
+
357
+ Environment files use **simple key-value merging** with precedence rules.
358
+
359
+ #### Merge Precedence
360
+
361
+ From lowest to highest priority:
362
+
363
+ 1. Base template `.env.example`
364
+ 2. Overlay `.env.example` files (in application order)
365
+ 3. Overlay imports (`.env` files from `.shared/`)
366
+ 4. Custom `.env.example` (if present)
367
+ 5. Manifest overrides (from preset `glueConfig.environment`)
368
+
369
+ **Rule**: Later sources override earlier sources for the same key.
370
+
371
+ #### Merge Algorithm
372
+
373
+ 1. Start with empty result
374
+ 2. For each source (in precedence order):
375
+ - Parse key=value pairs
376
+ - Add to result (overwriting existing keys)
377
+ 3. Generate combined `.env.example` with all sections
378
+ 4. If port offset specified, also generate `.env` with offset values
379
+
380
+ **Example**:
381
+
382
+ ```bash
383
+ # Base overlay .env.example
384
+ POSTGRES_VERSION=15
385
+ POSTGRES_PORT=5432
386
+
387
+ # Another overlay .env.example
388
+ POSTGRES_PORT=5433 # Conflict!
389
+ REDIS_PORT=6379
390
+
391
+ # Preset glueConfig.environment
392
+ POSTGRES_USER=myapp
393
+
394
+ # Result (later wins for conflicts)
395
+ POSTGRES_VERSION=15
396
+ POSTGRES_PORT=5433 # Overlay 2 wins
397
+ REDIS_PORT=6379
398
+ POSTGRES_USER=myapp # From preset
399
+ ```
400
+
401
+ #### Port Offset in .env Files
402
+
403
+ **Rule**: Variables matching pattern `*PORT*=\d+` are automatically offset.
404
+
405
+ **Example**:
406
+
407
+ ```bash
408
+ # .env.example (before offset)
409
+ POSTGRES_PORT=5432
410
+ GRAFANA_HTTP_PORT=3000
411
+ APP_NAME=myapp # Not affected
412
+
413
+ # .env (with offset=100)
414
+ POSTGRES_PORT=5532 # Offset applied
415
+ GRAFANA_HTTP_PORT=3100 # Offset applied
416
+ APP_NAME=myapp # Not affected
417
+ ```
418
+
419
+ ## Package Merging
420
+
421
+ ### apt-get-packages Feature
422
+
423
+ **Rule**: Space-separated package lists are split, merged, and deduplicated.
424
+
425
+ **Example**:
426
+
427
+ ```json
428
+ // Base
429
+ {
430
+ "features": {
431
+ "ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
432
+ "packages": "curl wget"
433
+ }
434
+ }
435
+ }
436
+
437
+ // Overlay
438
+ {
439
+ "features": {
440
+ "ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
441
+ "packages": "wget jq"
442
+ }
443
+ }
444
+ }
445
+
446
+ // Result (deduplicated)
447
+ {
448
+ "features": {
449
+ "ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
450
+ "packages": "curl wget jq"
451
+ }
452
+ }
453
+ }
454
+ ```
455
+
456
+ ### cross-distro-packages Feature
457
+
458
+ **Rule**: Both `apt` and `apk` package lists are merged independently.
459
+
460
+ **Example**:
461
+
462
+ ```json
463
+ // Base
464
+ {
465
+ "features": {
466
+ "./features/cross-distro-packages": {
467
+ "apt": "build-essential wget",
468
+ "apk": "build-base wget"
469
+ }
470
+ }
471
+ }
472
+
473
+ // Overlay
474
+ {
475
+ "features": {
476
+ "./features/cross-distro-packages": {
477
+ "apt": "wget curl",
478
+ "apk": "wget curl"
479
+ }
480
+ }
481
+ }
482
+
483
+ // Result (each distro merged independently)
484
+ {
485
+ "features": {
486
+ "./features/cross-distro-packages": {
487
+ "apt": "build-essential wget curl",
488
+ "apk": "build-base wget curl"
489
+ }
490
+ }
491
+ }
492
+ ```
493
+
494
+ ## Lifecycle Commands
495
+
496
+ ### postCreateCommand and postStartCommand
497
+
498
+ **Rule**: Commands are **concatenated** with `&&` operator, not merged.
499
+
500
+ **Example**:
501
+
502
+ ```json
503
+ // Base
504
+ {
505
+ "postCreateCommand": "npm install"
506
+ }
507
+
508
+ // Overlay
509
+ {
510
+ "postCreateCommand": "bash setup-nodejs.sh"
511
+ }
512
+
513
+ // Result (concatenated)
514
+ {
515
+ "postCreateCommand": "npm install && bash setup-nodejs.sh"
516
+ }
517
+ ```
518
+
519
+ **Note**: Scripts are copied to `.devcontainer/scripts/` and referenced, not inlined.
520
+
521
+ ## Application Order
522
+
523
+ Overlays are applied in **category order** to ensure consistent precedence:
524
+
525
+ 1. Base template (plain or compose)
526
+ 2. Language overlays
527
+ 3. Database overlays
528
+ 4. Observability overlays
529
+ 5. Cloud tool overlays
530
+ 6. Dev tool overlays
531
+ 7. Custom patches (if present)
532
+
533
+ **Within each category**: Overlays are applied in the order specified in the manifest/answers.
534
+
535
+ ## Edge Cases and Special Handling
536
+
537
+ ### Empty Arrays
538
+
539
+ **Rule**: Empty arrays in source do **not** clear target arrays.
540
+
541
+ **Example**:
542
+
543
+ ```json
544
+ // Base
545
+ {
546
+ "forwardPorts": [3000, 8080]
547
+ }
548
+
549
+ // Overlay with empty array
550
+ {
551
+ "forwardPorts": []
552
+ }
553
+
554
+ // Result (base preserved, not cleared)
555
+ {
556
+ "forwardPorts": [3000, 8080]
557
+ }
558
+ ```
559
+
560
+ **Rationale**: Empty arrays in overlays are typically omitted fields, not intentional clearing.
561
+
562
+ ### Null Values
563
+
564
+ **Rule**: `null` values in source **overwrite** target values.
565
+
566
+ **Example**:
567
+
568
+ ```json
569
+ // Base
570
+ {
571
+ "workspaceFolder": "/workspace"
572
+ }
573
+
574
+ // Overlay
575
+ {
576
+ "workspaceFolder": null
577
+ }
578
+
579
+ // Result (null overwrites)
580
+ {
581
+ "workspaceFolder": null
582
+ }
583
+ ```
584
+
585
+ ### Undefined vs Missing Keys
586
+
587
+ **Rule**: Missing keys in source have **no effect** on target. Only explicitly defined keys are merged.
588
+
589
+ **Example**:
590
+
591
+ ```json
592
+ // Base
593
+ {
594
+ "name": "My Container",
595
+ "workspaceFolder": "/workspace"
596
+ }
597
+
598
+ // Overlay (missing 'name')
599
+ {
600
+ "workspaceFolder": "/app"
601
+ }
602
+
603
+ // Result ('name' preserved)
604
+ {
605
+ "name": "My Container",
606
+ "workspaceFolder": "/app"
607
+ }
608
+ ```
609
+
610
+ ## Conflict Detection
611
+
612
+ ### When Conflicts Occur
613
+
614
+ Conflicts are **warnings**, not errors. The merge still succeeds with the last writer winning.
615
+
616
+ **Scenarios that generate warnings**:
617
+
618
+ 1. Same environment variable with different values (except PORT variables with offset)
619
+ 2. Same port forwarded with conflicting attributes
620
+ 3. Incompatible feature configurations
621
+
622
+ **Scenarios that do NOT conflict**:
623
+
624
+ - Same package in multiple overlays (union behavior)
625
+ - Same port in multiple overlays (deduplicated)
626
+ - Same PATH component in multiple overlays (deduplicated)
627
+
628
+ ### Conflict Resolution
629
+
630
+ **Doctor command validation** checks for potential conflicts:
631
+
632
+ ```bash
633
+ $ container-superposition doctor
634
+
635
+ ⚠️ Warning: Environment variable conflict
636
+ POSTGRES_PORT defined in multiple overlays:
637
+ - postgres: 5432
638
+ - custom: 5433
639
+ Resolution: custom value (5433) used
640
+ ```
641
+
642
+ ## Standards and References
643
+
644
+ This merge strategy is informed by:
645
+
646
+ - **RFC 7386**: JSON Merge Patch specification
647
+ - We use deep merge (more powerful than shallow merge patch)
648
+ - Objects are merged recursively
649
+ - Arrays use union strategy (not replacement)
650
+
651
+ - **Docker Compose Specification**:
652
+ - Service-based merging follows Docker Compose extend semantics
653
+ - Array fields are concatenated per Compose specification
654
+
655
+ - **DevContainer Specification**:
656
+ - Features object merging follows devcontainer.json schema
657
+ - Lifecycle commands follow devcontainer command chaining
658
+
659
+ ## Testing Strategy
660
+
661
+ All merge behaviors are validated with **golden tests**:
662
+
663
+ 1. **Unit tests**: Test individual merge functions (deepMerge, mergeRemoteEnv, etc.)
664
+ 2. **Integration tests**: Test full overlay composition scenarios
665
+ 3. **Golden tests**: Snapshot expected outputs for complex merges
666
+ 4. **Doctor validation**: Runtime checks for merge correctness
667
+
668
+ See `tool/__tests__/merge-strategy.test.ts` for comprehensive test coverage.
669
+
670
+ ## Migration and Compatibility
671
+
672
+ ### Version Compatibility
673
+
674
+ - Merge behavior is **stable** across minor versions
675
+ - Breaking changes to merge strategy require **major version bump**
676
+ - Generated configurations include `manifestVersion` for future migration
677
+
678
+ ### Legacy Behavior
679
+
680
+ **Pre-0.1.0**: No formal merge specification
681
+
682
+ - Behavior was implicit and partially documented
683
+ - Some edge cases had undefined behavior
684
+
685
+ **0.1.0+**: Formal specification (this document)
686
+
687
+ - All merge behavior is deterministic and tested
688
+ - Doctor command validates merge correctness
689
+
690
+ ## Summary
691
+
692
+ The merge strategy is:
693
+
694
+ - **Deterministic**: Same inputs always produce same output
695
+ - **Explicit**: All rules are documented
696
+ - **Union-oriented**: Collections are merged, not replaced
697
+ - **Last-writer-wins**: For primitive conflicts
698
+ - **Field-aware**: Arrays have specific strategies per field
699
+
700
+ For implementation details, see `tool/utils/merge.ts` and `tool/questionnaire/composer.ts`.