@postman/sdk-config 0.0.0 → 0.0.2

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,328 @@
1
+ # SDK Config IR v1
2
+
3
+ `SdkConfigIrV1` is the normalized contract consumed by SDK generation. It replaces two legacy
4
+ configuration surfaces: sdk-gen-core `BuildParameters` and a selected Fern generator invocation.
5
+ It is intentionally smaller than the union of both legacy schemas.
6
+
7
+ The contract lives in `@postman/sdk-config` so sdk-gen-core, sdk-gen-api, Fern CLI, and other
8
+ producers can share the same runtime schema and inferred TypeScript types. The schema, types,
9
+ tests, documentation, and JSON fixtures are versioned together.
10
+
11
+ `tests/fixtures/sdk-config-ir/v1` is the canonical source for package-level contract fixtures.
12
+
13
+ ## Core request envelope
14
+
15
+ New producers send the transport discriminator alongside the existing artifact metadata:
16
+
17
+ ```ts
18
+ type SdkGenerationRequest =
19
+ | {
20
+ artifactId: number;
21
+ bucketName: string;
22
+ kind: 'legacyBuildParameters';
23
+ inputParams: BuildParameters;
24
+ }
25
+ | {
26
+ artifactId: number;
27
+ bucketName: string;
28
+ kind: 'sdkConfigIr';
29
+ sdkConfigIr: SdkConfigIrV1;
30
+ };
31
+ ```
32
+
33
+ The existing `{ artifactId, bucketName, inputParams }` production message remains valid and is
34
+ normalized internally to `legacyBuildParameters`. An explicit `kind` is required for new envelope
35
+ messages. Valid IR requests route only to the IR bridge and never fall through to legacy generation.
36
+
37
+ ## Authority and compatibility
38
+
39
+ - Normalized fields are authoritative. Generation must never read `compatibility.legacyInput` to
40
+ fill a missing normalized field or change behavior.
41
+ - `compatibility.legacyInput` temporarily retains the original `BuildParameters` or selected Fern
42
+ generator invocation for migration debugging.
43
+ - `compatibility.unsupportedFields` records legacy options that were not translated.
44
+ - `compatibility.outputProfile` preserves converged, Postman-legacy, or Fern-legacy output defaults
45
+ without introducing a permanent target flavor.
46
+ - `target.sourceOrigin` identifies where a request originated; it does not select output behavior.
47
+ - Credentials are references to externally managed secrets. Tokens and passwords do not belong in
48
+ normalized output or publication configuration.
49
+
50
+ ## Configuration design rules
51
+
52
+ - Each behavior has one canonical home regardless of its legacy origin or language spelling.
53
+ - Package and publication identity belongs in `package`; publication mechanics belong in
54
+ `output.publish`.
55
+ - Shared client behavior belongs in `client`; shared generator behavior belongs in `generation`.
56
+ - `generation.language` contains only behavior that remains language-specific after normalization.
57
+ - A simple on/off option is a boolean. A feature with additional settings is an object containing a
58
+ required `enabled` boolean so settings can be added without changing its shape.
59
+ - User-defined maps are permitted only when their values have a stable schema. The temporary
60
+ `legacyInput.value` JSON object is the deliberate migration exception.
61
+
62
+ Examples of the boolean rule:
63
+
64
+ - Boolean: `generation.webSockets`, `generation.multipleResponses`.
65
+ - Configurable feature: `client.retry`, `client.tokenRefresh`, `docs.snippets`,
66
+ `generation.analytics`, `generation.hooks`, `generation.streams`, and `generation.wireTests`.
67
+
68
+ ## Cross-origin deduplication decisions
69
+
70
+ The audit compared sdk-gen-core inputs, Fern generator schemas, and 61 customer Fern projects.
71
+ Legacy aliases below intentionally converge on one normalized behavior.
72
+
73
+ | Behavior | Postman input | Fern input | Canonical field |
74
+ | ------------------------ | ------------------------------------------------------------------ | ----------------------------------------------------- | ------------------------------------------------------------------------------- |
75
+ | Response validation | language `validateResponses` | `skipResponseValidation`, `skip_validation` | `client.responseValidation` (Fern values are inverted) |
76
+ | Request parameter shape | generator defaults | `inline_request_params` | `client.requestParameterStyle` |
77
+ | Path parameter shape | generator defaults | importer and generator `inlinePathParameters` aliases | `client.pathParameterStyle` |
78
+ | File property shape | generator defaults | `inlineFileProperties` aliases | `client.filePropertyStyle` |
79
+ | Enum representation | TypeScript `generateEnumAs` | `enum_type` and forward-compatible enum aliases | `generation.serialization.enumRepresentation` |
80
+ | Additional properties | generator defaults | Python/Pydantic `extra_fields` aliases | `generation.serialization.additionalProperties` |
81
+ | Inline models | generator defaults | `enableInlineTypes` and language aliases | `generation.serialization.inlineTypes` |
82
+ | Client/error/pager names | `sdkName` plus generator conventions | language-specific class-name aliases | `target.sdkName` and `generation.naming` |
83
+ | Package namespace | npm organization and generator conventions | namespace, package-prefix, and root-namespace aliases | `package.namespace` |
84
+ | Package identity | per-language package IDs | registry output and package-name aliases | `package.packageName`, `moduleName`, `modulePath`, `groupId`, or `artifactId` |
85
+ | Extra dependencies | language `hookDependencies` and package metadata | dependency and dev-dependency aliases | `package.extraDependencies`, `extraDevDependencies`, or `extraPeerDependencies` |
86
+ | Constructor parameters | language `additionalConstructorParameters` / `additionalVariables` | generator-specific constructor options | `client.additionalConstructorParameters` |
87
+ | Repository delivery | `deliveryMethods`, `githubRepoName`, `githubRef`, `privateRepo` | GitHub output owner/repository settings | `output.delivery` and `output.github` |
88
+ | Registry publication | per-language package identity plus publishing settings | registry output settings | `package` identity plus `output.publish` mechanics |
89
+
90
+ Fields are not duplicated under `generation.language` when a shared row above can represent them.
91
+ Translators are responsible for alias resolution, unit conversion, and inverted legacy booleans.
92
+
93
+ ## Source and target boundaries
94
+
95
+ - `source.specs` is the only source shape. One spec is a one-element array.
96
+ - Spec order is authoritative for multi-spec builds. Optional IDs must be unique; namespaces may
97
+ repeat. The combination layer must reject unresolved path and component collisions.
98
+ - Source formats are limited to formats sdk-gen-core can consume. Other Fern inputs must be lowered
99
+ before submission.
100
+ - A Kotlin target currently uses the Java/JVM generator with Kotlin-facing documentation, snippets,
101
+ and examples. Its retained JVM options share the Java implementation.
102
+ - `target.language` is the generator identity; `generatorName` is not normalized.
103
+ - `target.sdkVersion` and `target.generatorVersion` are the effective versions for
104
+ `target.language`. Producers resolve language-specific overrides before global values and emit
105
+ the result on each one-language IR request; version maps do not belong under
106
+ `generation.language`.
107
+ - `target.apiVersion` retains shared API version metadata on every emitted target.
108
+ - `target.generatorVersion` optionally pins one exact customer-facing generator version. Omission
109
+ remains distinct so the orchestrator can resolve the latest supported version.
110
+ - Fern provenance remains available in `compatibility.legacyInput` when needed for debugging.
111
+
112
+ ## Postman `BuildParameters` migration map
113
+
114
+ | Legacy field | SDK Config IR v1 | Notes |
115
+ | ---------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------- |
116
+ | `specUrl` | `source.specs[].specUrl` | A legacy request creates a one-element array. |
117
+ | `languages[]` | `target.language` | Translators emit one IR request per target. |
118
+ | `sdkName` | `target.sdkName` | Public SDK identity, not package registry identity. |
119
+ | `sdkVersion` / language `sdkVersion` | `target.sdkVersion` | Language override is resolved before emitting IR. |
120
+ | `apiVersion` | `target.apiVersion` | API version metadata. |
121
+ | `org` | `target.organization` | Source organization metadata. |
122
+ | `baseUrl` | `api.baseUrl` | Default API URL. |
123
+ | `environments` | `api.environments` | Every environment has a non-empty `urls` array; a single URL becomes one entry. |
124
+ | `environmentVariables` | `api.environmentVariables` | Declarations; auth/header fields reference them by name. |
125
+ | `auth` + `authentication` | `api.auth` | Legacy flags and settings become typed schemes and requirements. |
126
+ | `refreshToken` | `client.tokenRefresh` | Response keys map to `accessTokenField` and `refreshTokenField`. |
127
+ | `retry` | `client.retry` | Millisecond units are explicit in normalized names. |
128
+ | `responseHeaders` | `client.responseHeaders` | Shared across languages. |
129
+ | `multiTenant` | `client.multiTenant` | Shared client behavior. |
130
+ | TypeScript `httpClient` | `generation.language.typescript.httpClient` | Only `fetch` and `axios` are selectable; other languages use their generator transport. |
131
+ | `additionalConstructorParameters` | `client.additionalConstructorParameters` | Merged from language options. |
132
+ | `deliveryMethods` | `output.delivery` | Normalized to `files`, `zip`, or `github`; multiple deliveries produce separate IR requests. |
133
+ | `fileOutput` | `output.path` or `output.fileName` | Depends on delivery type. |
134
+ | `githubRef` | `output.github.branch` | Only meaningful for GitHub delivery. |
135
+ | `privateRepo` | `output.github.privateRepository` | Only meaningful for GitHub delivery. |
136
+ | `publishing.githubOrg` | `output.github.repository` | Translator combines the organization and repository name. |
137
+ | package names and IDs | `package` | Includes npm/PyPI/NuGet/RubyGems/Crates/Composer names and Maven IDs. |
138
+ | `goModuleName` | `package.modulePath` | Go publication and import identity. |
139
+ | `license`, `authors`, `developers`, `homepage` | `package` | Manifest metadata. |
140
+ | `documentation.url` | `docs.referenceBaseUrl` | Base URL for generated reference-file links. |
141
+ | `documentation.snippets` | `docs.snippets` | `enabled` is explicit when the object is present. |
142
+ | `docs[]` | `docs` | Legacy API/snippet selections are lowered to typed documentation settings. |
143
+ | `includeWatermark` | `generation.includeWatermark` | Simple on/off behavior. |
144
+ | `includeOptionalSnippetParameters` | `generation.includeOptionalSnippetParameters` | Simple on/off behavior. |
145
+ | `inferServiceNames` | `generation.inferServiceNames` | Simple on/off behavior. |
146
+ | `buildAllModels` | `generation.buildAllModels` | Simple on/off behavior. |
147
+ | `deprecatedOperations.include` | `generation.includeDeprecatedOperations` | Simplified to a boolean. |
148
+ | `multipleResponses.enabled` | `generation.multipleResponses` | Simplified to a boolean. |
149
+ | `devContainer` | `generation.devContainer` | Simple on/off behavior. |
150
+ | `ignoreFiles` / language `ignore` | `generation.ignoreFiles` | Language aliases are resolved by the translator. |
151
+ | `reservedKeywords` | `generation.reservedKeywords` | Shared generator input. |
152
+ | `hooks`, `hookDependencies`, `hooksLocation` | `generation.hooks` | Location and dependencies are one configurable feature. |
153
+ | `customCode*`, previous artifact location | `generation.customCode` | Locations become typed generation assets. |
154
+ | `workflow` | `generation.workflows[]` | The normalized shape supports more than one workflow. |
155
+ | `customQueries` | `generation.customQueryPaths` | Query definitions are represented as paths. |
156
+ | `analytics` | `generation.analytics` | `enabled` is explicit; timing fields use milliseconds. |
157
+ | language options | `generation.language.<target>` or shared fields | Package, naming, validation, and dependency aliases are lifted out first. |
158
+
159
+ ### Postman language-option migration map
160
+
161
+ | Legacy field | SDK Config IR v1 |
162
+ | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
163
+ | common `sdkVersion` | `target.sdkVersion` |
164
+ | common `ignore` | `generation.ignoreFiles` |
165
+ | common `githubRepoName` / `targetBranch` | `output.github.repository` / `output.github.branch` |
166
+ | common `hookDependencies` | `generation.hooks.dependencies` |
167
+ | `additionalConstructorParameters` / `additionalVariables` | `client.additionalConstructorParameters` |
168
+ | TypeScript `npmOrg` / `npmName` | `package.namespace` / `package.packageName` |
169
+ | TypeScript `httpClient` | `generation.language.typescript.httpClient` |
170
+ | TypeScript `generateEnumAs` | `generation.serialization.enumRepresentation` |
171
+ | TypeScript versions, compiler options, bundle, default export, naming strategy, scripts | `generation.language.typescript` |
172
+ | Python `pypiPackageName`, classifiers, and project URLs | `package.packageName`, `classifiers`, and `projectUrls` |
173
+ | Python Pydantic version and optional initialization | `generation.language.python` |
174
+ | Java/Kotlin `groupId` / `artifactId` | `package.groupId` / `package.artifactId` |
175
+ | Java/Kotlin `includeKotlinSnippets` | `generation.language.java.includeKotlinSnippets` |
176
+ | Java/Go/C#/Rust `validateResponses` | `client.responseValidation` |
177
+ | Go/CLI `goModuleName` | `package.modulePath` |
178
+ | C# `packageId` / `useOptionalWrapper` | `package.packageName` / `generation.language.csharp.useOptionalWrapper` |
179
+ | PHP `packageName` / `allowMockClient` | `package.packageName` / `generation.allowMockClient` |
180
+ | Rust package name and repository | `package.packageName` / `package.repository` |
181
+ | Swift `allowMockClient` | `generation.allowMockClient` |
182
+ | CLI pagination parameters / skills object | `generation.language.cli.paginationParameters` / `skills` boolean |
183
+ | language authors, developers, homepage, and repository | `package` metadata |
184
+
185
+ `usesFormData` is derived from the API and is not configuration. The legacy `ai` flag,
186
+ `generateMCP`, MCP language options, Terraform-provider options and plan modifiers, and Bash output
187
+ are outside the SDK Config IR v1 target set and must be reported as unsupported. Commented-out
188
+ custom-template and naming inputs were not restored as part of the normalized contract.
189
+
190
+ ## Fern common migration map
191
+
192
+ Customer-use counts below are distinct projects observed in the 61-project configuration snapshot
193
+ audited on 2026-07-17. Counts guide the initial contract; they are not a permanent support policy.
194
+
195
+ | Fern field or aliases | SDK Config IR v1 | Observed projects |
196
+ | --------------------------------------------------------------------------- | -------------------------------------------------- | -----------------------------------------: |
197
+ | `api.specs[]` | `source.specs[]` | 53 OpenAPI, 3 AsyncAPI |
198
+ | `api.specs[].namespace` | `source.specs[].namespace` | 1 |
199
+ | `api.specs[].overlays` | `source.specs[].overlays` | 2 |
200
+ | `api.specs[].overrides` | `source.specs[].overrides` | 39 |
201
+ | `api.default-url` | `api.baseUrl` | 21 |
202
+ | `api.default-environment` | `api.defaultEnvironment` | 22 |
203
+ | `api.environments` | `api.environments` | 20+ |
204
+ | `api.auth`, `api.auth-schemes` | `api.auth` | 27 |
205
+ | `api.headers` | `api.headers` | 11+ |
206
+ | generator `inlinePathParameters`, `inline-path-parameters`, and API setting | `client.pathParameterStyle` | 12 API; 1-3 per language |
207
+ | generator `inlineFileProperties`, `inline-file-properties` | `client.filePropertyStyle` | 1-3 per language |
208
+ | `inline_request_params` | `client.requestParameterStyle` | 6 Python |
209
+ | `customPagerName`, `custom-pager-name`, `custom_pager_name` | `generation.naming.pagerName` | 2-5 per language |
210
+ | timeout aliases in seconds or milliseconds | `client.timeoutMs` | Translator converts units. |
211
+ | `skipResponseValidation`, Python `skip_validation` | `client.responseValidation` | Inverted; 12 TS, 8 Python |
212
+ | `useDefaultRequestParameterValues` aliases | `client.useDefaultRequestParameterValues` | 1 |
213
+ | `enable-wire-tests`, `enableWireTests`, `generateWireTests` | `generation.wireTests` | 1-15 per language |
214
+ | WebSocket generation aliases | `generation.webSockets` | 3 TS, 3 Python |
215
+ | `streamType`, `fileResponseType` | `generation.streams` | 1 TS |
216
+ | client/exported/environment class aliases | `generation.naming` | 1-28 per language |
217
+ | base API/general exception class aliases | `generation.naming.apiErrorName` / `baseErrorName` | 3-6 Java/C# |
218
+ | namespace/package-prefix aliases | `package.namespace` | 1-30 per language |
219
+ | output registry package names | `package.packageName` | 5-28 per registry language |
220
+ | Python custom `package_name` | `package.moduleName` | 18 |
221
+ | Go `module.path` | `package.modulePath` | 3 |
222
+ | Maven `group` / `artifact` | `package.groupId` / `artifactId` | 7 |
223
+ | custom/extra dependencies | `package.extraDependencies` and related lists | Multiple languages |
224
+ | local output | `output.delivery: "files"` | 49+ |
225
+ | registry output | `output.publish` plus `package` identity | 5-28 per registry language |
226
+ | GitHub output | `output.delivery: "github"` and `output.github` | Translator resolves Fern owner/repo forms. |
227
+ | Fern README config | `docs.readme` | 12 API names; 11 disabled-section configs |
228
+ | `includeApiReference` | `docs.includeApiReference` | 10 TS |
229
+
230
+ The Rust language config was added after the initial audit. That audit compared existing Fern
231
+ _customer projects_, which surfaced no Rust-specific options; comparing the Fern Rust generator's
232
+ own schema later surfaced four with no shared equivalent — `dateTimeType` (`DateTime<FixedOffset>`
233
+ versus `DateTime<Utc>` on every datetime field), `capitalizeInitialisms`, `features`, and
234
+ `defaultFeatures`. Crate identity, client name, and package metadata deliberately stay on the
235
+ shared fields.
236
+
237
+ Fern README endpoint strings such as `GET /users` are parsed into
238
+ `{ "method": "GET", "path": "/users" }`; the legacy string shorthand is not part of the
239
+ normalized contract.
240
+
241
+ ### Retained Fern API-import settings
242
+
243
+ These settings remain under `source.apiImportSettings` because they affect spec conversion rather
244
+ than generated-language presentation.
245
+
246
+ | Fern setting | Normalized field | Observed projects |
247
+ | ----------------------------------------- | ------------------------------------ | ----------------: |
248
+ | `respect-nullable-schemas` | `respectNullableSchemas` | 10 |
249
+ | `title-as-schema-name` | `titleAsSchemaName` | 16 |
250
+ | `coerce-enums-to-literals` | `coerceEnumsToLiterals` | 14 |
251
+ | `idiomatic-request-names` | `idiomaticRequestNames` | 12 |
252
+ | `wrap-references-to-nullable-in-optional` | `wrapReferencesToNullableInOptional` | 9 |
253
+ | `coerce-optional-schemas-to-nullable` | `coerceOptionalSchemasToNullable` | 9 |
254
+ | `object-query-parameters` | `objectQueryParameters` | 9 |
255
+ | `only-include-referenced-schemas` | `onlyIncludeReferencedSchemas` | 4 |
256
+ | `group-multi-api-environments` | `groupMultiApiEnvironments` | 2 |
257
+ | `path-parameter-order` | `pathParameterOrder` | 2 |
258
+ | `type-dates-as-strings` | `typeDatesAsStrings` | 12 |
259
+
260
+ ## Fern language-specific migration map
261
+
262
+ Only options that remain language-specific stay in `generation.language`.
263
+
264
+ | Language | Fern fields retained in language config | Shared mappings lifted out |
265
+ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
266
+ | TypeScript | `packageManager` (1), `testFramework` (2), `allowCustomFetcher` (13), branded aliases (1), legacy exports (1), `useBigInt`, and positive `serdeLayer` from `noSerdeLayer` (8) | Namespace, validation, inline types, dependencies, wire tests, streams, WebSockets, pager/client names |
267
+ | Python | Python/Pydantic versions, client filenames (13), Pydantic compatibility, frozen models (1), required optionals (2), union naming (1), field aliases (3), always-initialize optionals, TypedDict requests (3) | Package/module names, enum form, extra fields, response validation, request style, dependencies, timeout, layout, wire tests, WebSockets |
268
+ | Java/Kotlin | Package layout (3), local dates (2), optional-nullable collapse (2), and Gradle settings (7) | Maven identity, namespace, names, forward-compatible enums, inline properties, dependencies, pager, wire tests |
269
+ | Go | Legacy complex models, union version (3), and legacy client options (1) | Module/package identity, client name, inline properties, pager, wire tests |
270
+ | C# | Optional wrapper, simplified dictionaries (1), explicit namespaces (7), core namespace behavior (1), exception handler (5) | NuGet identity, namespace, class/error names, pager, wire tests |
271
+ | PHP | Property access (2) and generated client interfaces (1) | Composer identity, namespace, client/pager names, request style, dependencies, wire tests |
272
+ | Ruby | Require paths (2) | Gem identity, client name, dependencies, pager, wire tests |
273
+ | Rust | Datetime representation, initialism casing, and Cargo features/default-features | Crate identity, client name, and package metadata use shared fields. |
274
+ | Swift | No retained language-specific options | Shared generation behavior is sufficient for observed configs. |
275
+ | CLI | Pagination parameter names and the `skills` boolean | Go module and package identity use `package`. |
276
+
277
+ ## Removed and deferred legacy configuration
278
+
279
+ Removed fields are not copied into another open-ended normalized bag. Translators report them in
280
+ `compatibility.unsupportedFields`; their original value remains available in `legacyInput` during
281
+ the migration.
282
+
283
+ ### Removed after finding no customer use and no sdk-gen-core equivalent
284
+
285
+ - Common Fern generation: include/omit platform-header overrides. Output-profile defaults own this
286
+ compatibility behavior instead of a second permanent switch.
287
+ - Source import: optional-additional-properties defaults, environment grouping strategies,
288
+ discriminant removal, schema-collision recovery, forward-compatible inference, default
289
+ environment inference, const coercion, endpoint filters, example-depth controls, alias
290
+ resolution, single-schema oneOf preservation, readonly/read variants, binary-response import
291
+ coercion, form encoding defaults, additional-properties defaults, inline-allOf, default integer
292
+ formats, discriminated-union inference, request-name disambiguation, and AsyncAPI message naming.
293
+ - TypeScript: linter/formatter selection, consolidated type files, subpackage exports, and endpoint
294
+ metadata.
295
+ - Python: ORM/smart-union/coercion/wrapped-alias/positional-constructor/inheritance/validator
296
+ switches, flattened union bodies, stream chunk size, recursion limit, lazy imports, and custom
297
+ transport.
298
+ - Java/JVM: JSON include, required-builder checks, public/extensible constructors, unknown-as-node,
299
+ nullable annotations, and custom interceptors.
300
+ - C#: union implementation switches, root-client access, generated error-type toggle, interceptor
301
+ class, unified options, response-body redaction, solution format, and custom output paths.
302
+ - Go: toolchain version, import aliases, custom constructor name, error-code layout, empty wrapper,
303
+ byte reader, getter, union-base deduplication, and root-request export switches.
304
+ - Ruby: custom client module and RuboCop switches.
305
+ - Rust and Swift: all previously proposed language-specific switches had no observed customer use.
306
+
307
+ ### Observed but deliberately deferred as bespoke or low-use
308
+
309
+ | Legacy configuration | Observed projects | Reason |
310
+ | ----------------------------------------------------------- | ----------------: | ------------------------------------------------------------------------ |
311
+ | TypeScript arbitrary `packageJson` exports/scripts | 2 | Open-ended manifest rewriting conflicts with the typed package contract. |
312
+ | TypeScript `outputEsm`, fetch runtime, form-data runtime | 1-2 | Runtime-specific and not required by sdk-gen-core today. |
313
+ | Python additional `__init__` exports / excluded exports | 3 / 6 | File-layout customization with significant generator coupling. |
314
+ | Python improved imports and file-upload-only TypedDict mode | 1-3 | Experimental or narrow compatibility behavior. |
315
+ | Java custom Gradle plugins / include-wrapper | 1 | Arbitrary build-file injection or one-customer behavior. |
316
+ | C# experimental fully qualified/version namespaces | 1 | Experimental one-customer behavior. |
317
+ | PHP arbitrary Composer autoload files | 2 | Open-ended manifest rewriting; typed dependencies remain supported. |
318
+ | Ruby flattened module structure | 1 | One-customer layout behavior. |
319
+
320
+ Adding one of these fields later is additive. Retaining every historical generator flag now would
321
+ make v1 expensive to implement and would turn temporary compatibility behavior into a permanent
322
+ shared contract.
323
+
324
+ ## Tests and fixtures
325
+
326
+ The contract tests are grouped by root contract, source, output/publication, and normalization.
327
+ Valid single- and multi-spec inputs are JSON fixtures so future consumers can reuse the same
328
+ language-neutral examples. Invalid cases remain next to their expected validation errors.