@workos/oagen 0.6.0 → 0.8.0
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.
- package/dist/cli/index.mjs +1339 -451
- package/dist/cli/index.mjs.map +1 -1
- package/dist/compat/index.d.mts +144 -57
- package/dist/compat/index.d.mts.map +1 -1
- package/dist/compat/index.mjs +365 -35
- package/dist/compat/index.mjs.map +1 -1
- package/dist/{diff-CltJMcwG.mjs → diff-BaH9qa8k.mjs} +100 -23
- package/dist/diff-BaH9qa8k.mjs.map +1 -0
- package/dist/differ-CTl98yoS.d.mts +116 -0
- package/dist/differ-CTl98yoS.d.mts.map +1 -0
- package/dist/index.d.mts +4 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +177 -4
- package/dist/index.mjs.map +1 -1
- package/dist/{differ-CG5ZErKl.mjs → overlay-TYxHOCow.mjs} +1112 -553
- package/dist/overlay-TYxHOCow.mjs.map +1 -0
- package/dist/scripts/smoke/baseline.mjs +176 -3
- package/dist/scripts/smoke/baseline.mjs.map +1 -1
- package/dist/smoke.d.mts +43 -0
- package/dist/smoke.d.mts.map +1 -1
- package/dist/smoke.mjs +176 -3
- package/dist/smoke.mjs.map +1 -1
- package/dist/tree-sitter-C53w_0WE.mjs.map +1 -1
- package/dist/{types-C7g5sLuX.d.mts → types-mTtkgy_h.d.mts} +213 -2
- package/dist/types-mTtkgy_h.d.mts.map +1 -0
- package/dist/verify/index.d.mts +4 -3
- package/dist/verify/index.d.mts.map +1 -1
- package/dist/verify/index.mjs +12 -10
- package/dist/verify/index.mjs.map +1 -1
- package/docs/architecture/emitter-contract.md +2 -2
- package/docs/architecture/extractor-contract.md +6 -5
- package/docs/architecture/pipeline.md +4 -3
- package/docs/architecture/workflows.md +5 -3
- package/docs/cli.md +148 -6
- package/docs/contributor/public-api.md +1 -1
- package/docs/core/compatibility-ir.md +200 -0
- package/docs/core/compatibility-policy.md +170 -0
- package/docs/core/compatibility.md +83 -0
- package/docs/core/index.md +5 -0
- package/docs/core/manifest-schema.md +76 -0
- package/docs/core/quickstart.md +9 -2
- package/docs/core/verify-compat.md +235 -0
- package/package.json +1 -1
- package/skills/generate-emitter/SKILL.md +12 -7
- package/skills/generate-emitter/references/project-scaffold.md +27 -6
- package/skills/generate-extractor/SKILL.md +14 -8
- package/skills/generate-sdk/SKILL.md +2 -2
- package/skills/generate-smoke-test/SKILL.md +9 -5
- package/skills/review-operations/SKILL.md +2 -2
- package/skills/verify-compat/SKILL.md +1 -1
- package/dist/diff-CltJMcwG.mjs.map +0 -1
- package/dist/differ-CG5ZErKl.mjs.map +0 -1
- package/dist/types-C7g5sLuX.d.mts.map +0 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Compatibility Policy
|
|
2
|
+
|
|
3
|
+
Human-authored compatibility policy lives in the `compat` section of `oagen.config.ts`. This is the single place for fail thresholds, language overrides, and intentional break approvals.
|
|
4
|
+
|
|
5
|
+
## Config Schema
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
export default {
|
|
9
|
+
compat: {
|
|
10
|
+
failOn: "breaking",
|
|
11
|
+
reportPath: "compat-report.json",
|
|
12
|
+
explain: true,
|
|
13
|
+
baselinePath: "sdk-surface.json",
|
|
14
|
+
languagePolicy: {
|
|
15
|
+
php: {
|
|
16
|
+
// optional sparse overrides
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
allow: [
|
|
20
|
+
// intentional break approvals
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Fields
|
|
27
|
+
|
|
28
|
+
| Field | Type | Default | Description |
|
|
29
|
+
| ---------------- | ------------------------------------------------ | ------------ | ------------------------------------------ |
|
|
30
|
+
| `failOn` | `'none' \| 'breaking' \| 'soft-risk'` | `'breaking'` | Severity threshold for `verify` failure |
|
|
31
|
+
| `reportPath` | `string` | — | Path to write machine-readable JSON report |
|
|
32
|
+
| `explain` | `boolean` | `false` | Include provenance explanations in output |
|
|
33
|
+
| `baselinePath` | `string` | — | Path to baseline compatibility snapshot |
|
|
34
|
+
| `languagePolicy` | `Record<LanguageId, Partial<CompatPolicyHints>>` | — | Per-language policy overrides |
|
|
35
|
+
| `allow` | `CompatApproval[]` | — | Intentional break approvals |
|
|
36
|
+
|
|
37
|
+
## Language Policy
|
|
38
|
+
|
|
39
|
+
Language policy determines which aspects of the public API are breaking. Built-in defaults capture language semantics:
|
|
40
|
+
|
|
41
|
+
| Language | Caller Uses Param Names | Constructor Order | Param Names Public | Overloads Public | Arity Public |
|
|
42
|
+
| -------- | ----------------------- | ----------------- | ------------------ | ---------------- | ------------ |
|
|
43
|
+
| PHP | yes | yes | yes | no | no |
|
|
44
|
+
| Python | yes | yes | yes | no | no |
|
|
45
|
+
| Ruby | yes | yes | yes | no | no |
|
|
46
|
+
| Go | no | yes | no | no | yes |
|
|
47
|
+
| Kotlin | yes | no | yes | yes | no |
|
|
48
|
+
| .NET | yes | no | yes | yes | no |
|
|
49
|
+
| Elixir | yes | no | yes | no | yes |
|
|
50
|
+
| Rust | no | yes | no | no | yes |
|
|
51
|
+
| Node | no | no | no | no | no |
|
|
52
|
+
|
|
53
|
+
### Overriding Language Defaults
|
|
54
|
+
|
|
55
|
+
Use `languagePolicy` only when a specific SDK intentionally diverges from language norms:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
compat: {
|
|
59
|
+
languagePolicy: {
|
|
60
|
+
node: {
|
|
61
|
+
// This Node SDK uses positional args, not options objects
|
|
62
|
+
methodParameterNamesArePublicApi: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Approvals
|
|
69
|
+
|
|
70
|
+
Approvals are concept-first: one approval covers one conceptual change across affected languages.
|
|
71
|
+
|
|
72
|
+
### Schema
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
interface CompatApproval {
|
|
76
|
+
symbol: string; // Fully-qualified symbol
|
|
77
|
+
category: string; // Change category (e.g., 'parameter_renamed')
|
|
78
|
+
appliesTo?: string[]; // Language IDs, or omit for all
|
|
79
|
+
match?: {
|
|
80
|
+
// Optional narrowing
|
|
81
|
+
parameter?: string;
|
|
82
|
+
member?: string;
|
|
83
|
+
oldName?: string;
|
|
84
|
+
newName?: string;
|
|
85
|
+
};
|
|
86
|
+
allowedReleaseLevel?: "major" | "minor" | "patch";
|
|
87
|
+
reason: string; // Required explanation
|
|
88
|
+
issue?: string; // Issue tracker reference
|
|
89
|
+
expiresAfterVersion?: string;
|
|
90
|
+
approved?: boolean; // Whether this approval is active (default: true)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### The `approved` Field
|
|
95
|
+
|
|
96
|
+
Approvals default to active. Set `approved: false` to deactivate an approval without removing it from the config — useful for temporarily disabling an approval or keeping a record of past approvals.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
{
|
|
100
|
+
symbol: 'Authorization.check',
|
|
101
|
+
category: 'parameter_renamed',
|
|
102
|
+
reason: 'Intentional rename for v3',
|
|
103
|
+
issue: 'SDK-1234',
|
|
104
|
+
approved: false, // deactivated — this break is no longer approved
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
When `approved` is `false`, the matching engine skips the approval entirely. When `approved` is `true` or omitted, the approval is active.
|
|
109
|
+
|
|
110
|
+
### Example
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
compat: {
|
|
114
|
+
allow: [
|
|
115
|
+
{
|
|
116
|
+
symbol: 'WorkOS\\Service\\UserManagement::createUser',
|
|
117
|
+
category: 'parameter_removed',
|
|
118
|
+
match: { parameter: 'passwordHashType' },
|
|
119
|
+
appliesTo: ['php', 'python', 'kotlin', 'dotnet'],
|
|
120
|
+
allowedReleaseLevel: 'major',
|
|
121
|
+
reason: 'Intentional wrapper-object migration',
|
|
122
|
+
issue: 'SDK-1234',
|
|
123
|
+
expiresAfterVersion: '6.0.0',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Matching Rules
|
|
130
|
+
|
|
131
|
+
Approvals must be narrow:
|
|
132
|
+
|
|
133
|
+
**Good:**
|
|
134
|
+
|
|
135
|
+
- One symbol, one category, one conceptual change
|
|
136
|
+
- Optionally one parameter/member
|
|
137
|
+
- Optionally a bounded set of affected languages
|
|
138
|
+
|
|
139
|
+
**Bad (rejected by validation):**
|
|
140
|
+
|
|
141
|
+
- Wildcard symbols (`*`, `Authorization.*`)
|
|
142
|
+
- Empty reason
|
|
143
|
+
- Missing symbol or category
|
|
144
|
+
|
|
145
|
+
### Anti-patterns
|
|
146
|
+
|
|
147
|
+
| Pattern | Problem |
|
|
148
|
+
| ---------------------------------- | ---------------------------------- |
|
|
149
|
+
| Approve all breaks in one language | Too broad — masks real regressions |
|
|
150
|
+
| Approve all parameter changes | Hides future unintentional breaks |
|
|
151
|
+
| No reason field | No audit trail |
|
|
152
|
+
| No issue reference | Can't trace back to decisions |
|
|
153
|
+
|
|
154
|
+
## Severity Determination
|
|
155
|
+
|
|
156
|
+
The default severity for a category comes from the classification engine. Language policy can modify this:
|
|
157
|
+
|
|
158
|
+
1. A `parameter_renamed` change is `breaking` by default
|
|
159
|
+
2. If the language policy says `methodParameterNamesArePublicApi: false` (e.g., Go), the classifier can downgrade to `soft-risk`
|
|
160
|
+
3. Approvals can further suppress changes from the failure threshold
|
|
161
|
+
|
|
162
|
+
## Fail Threshold
|
|
163
|
+
|
|
164
|
+
The `failOn` level determines which unapproved changes cause `oagen verify` to fail:
|
|
165
|
+
|
|
166
|
+
| Level | Fails on |
|
|
167
|
+
| ----------- | ---------------------------------------- |
|
|
168
|
+
| `none` | Never fails for compat |
|
|
169
|
+
| `breaking` | Unapproved breaking changes |
|
|
170
|
+
| `soft-risk` | Unapproved breaking OR soft-risk changes |
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Compatibility Verification
|
|
2
|
+
|
|
3
|
+
oagen includes a cross-language compatibility engine that detects breaking changes, classifies their severity per-language, and enforces policy before generated SDKs ship.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The compatibility system answers: **"will this regeneration break existing callers?"**
|
|
8
|
+
|
|
9
|
+
It works by comparing two snapshots of an SDK's public API surface — a baseline (the live SDK) and a candidate (the newly generated output) — and classifying every difference.
|
|
10
|
+
|
|
11
|
+
## Lifecycle
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
compat-extract → .oagen-compat-snapshot.json → compat-diff → report → compat-summary
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
1. **Extract** — `oagen compat-extract` runs an extractor against the live SDK and writes a `CompatSnapshot` JSON file to disk (`.oagen-compat-snapshot.json`)
|
|
18
|
+
2. **Diff** — `oagen compat-diff` compares a baseline snapshot (committed) against a candidate snapshot (freshly extracted from generated output), classifying each difference
|
|
19
|
+
3. **Enforce** — `compat-diff` exits non-zero when unapproved changes meet or exceed the fail threshold (`--fail-on`)
|
|
20
|
+
4. **Summary** — `oagen compat-summary` formats the JSON report as a markdown PR comment
|
|
21
|
+
|
|
22
|
+
The baseline snapshot is committed to the repository and updated on each release. The candidate snapshot is extracted in CI from the freshly generated SDK.
|
|
23
|
+
|
|
24
|
+
### CLI Commands
|
|
25
|
+
|
|
26
|
+
| Command | Purpose |
|
|
27
|
+
| ---------------------- | ---------------------------------------------- |
|
|
28
|
+
| `oagen compat-extract` | Extract a snapshot from a live SDK → JSON file |
|
|
29
|
+
| `oagen compat-diff` | Diff two snapshot files → classified report |
|
|
30
|
+
| `oagen compat-summary` | Format a report → markdown PR comment |
|
|
31
|
+
|
|
32
|
+
See [CLI Reference](../cli.md) for full argument tables.
|
|
33
|
+
|
|
34
|
+
## Relation to Smoke Tests
|
|
35
|
+
|
|
36
|
+
Smoke tests verify **behavioral** correctness: does the generated SDK call the right endpoints with the right parameters? Compatibility verification checks **structural** correctness: does the generated SDK preserve the public API shape that callers depend on?
|
|
37
|
+
|
|
38
|
+
Both run during `oagen verify`. Smoke tests catch bugs. Compat checks catch breaking changes.
|
|
39
|
+
|
|
40
|
+
## Relation to Spec Diffing
|
|
41
|
+
|
|
42
|
+
`oagen diff` compares two OpenAPI specs to show what changed at the API level. Compat verification compares two SDK surfaces to show what changed at the SDK level. A spec change may or may not produce a breaking SDK change, depending on the language and how the emitter handles it.
|
|
43
|
+
|
|
44
|
+
## Key Concepts
|
|
45
|
+
|
|
46
|
+
### Classification
|
|
47
|
+
|
|
48
|
+
Every detected change gets a category and severity:
|
|
49
|
+
|
|
50
|
+
- **Breaking** — Will break existing callers (e.g., `parameter_removed`, `symbol_renamed`)
|
|
51
|
+
- **Soft-risk** — May affect callers depending on usage (e.g., `constructor_reordered_named_friendly`)
|
|
52
|
+
- **Additive** — Safe to ship (e.g., `symbol_added`, `parameter_added_optional_terminal`)
|
|
53
|
+
|
|
54
|
+
See [Compatibility IR](compatibility-ir.md) for the full category list.
|
|
55
|
+
|
|
56
|
+
### Language Policy
|
|
57
|
+
|
|
58
|
+
The same conceptual change has different severity in different languages. A parameter rename is breaking in PHP (named arguments) but soft-risk in Go (positional only). Language policy is defined by `CompatPolicyHints` with built-in defaults for all 9 supported languages.
|
|
59
|
+
|
|
60
|
+
See [Compatibility Policy](compatibility-policy.md) for details.
|
|
61
|
+
|
|
62
|
+
### Concept-First Approvals
|
|
63
|
+
|
|
64
|
+
When a breaking change is intentional, it can be approved in `oagen.config.ts`. Approvals are concept-first: one approval targets one symbol and one change category, optionally scoped to specific languages.
|
|
65
|
+
|
|
66
|
+
See [Compatibility Policy](compatibility-policy.md) for the approval schema.
|
|
67
|
+
|
|
68
|
+
### Provenance
|
|
69
|
+
|
|
70
|
+
Each classified change includes a provenance bucket explaining where the drift came from:
|
|
71
|
+
|
|
72
|
+
- `spec_shape_change` — The spec changed
|
|
73
|
+
- `emitter_template_change` — The emitter template changed
|
|
74
|
+
- `normalization_change` — Naming normalization produced different output
|
|
75
|
+
- `unknown` — Source of drift not determined
|
|
76
|
+
|
|
77
|
+
## Further Reading
|
|
78
|
+
|
|
79
|
+
- [CLI Reference](../cli.md) — `compat-extract`, `compat-diff`, `compat-summary` argument tables
|
|
80
|
+
- [Compatibility IR](compatibility-ir.md) — Types, categories, parameters, `extractSnapshot()`
|
|
81
|
+
- [Compatibility Policy](compatibility-policy.md) — Config, approvals, language defaults
|
|
82
|
+
- [Manifest Schema](manifest-schema.md) — `.oagen-manifest.json` format
|
|
83
|
+
- [Verify Compat](verify-compat.md) — CI workflows, reports, GitHub Actions example
|
package/docs/core/index.md
CHANGED
|
@@ -8,5 +8,10 @@ These documents define the stable center of oagen as an OSS framework.
|
|
|
8
8
|
- [Pipeline Architecture](../architecture/pipeline.md)
|
|
9
9
|
- [IR Type System Reference](../architecture/ir-types.md)
|
|
10
10
|
- [Emitter Contract](../architecture/emitter-contract.md)
|
|
11
|
+
- [Compatibility Verification](compatibility.md)
|
|
12
|
+
- [Compatibility IR](compatibility-ir.md)
|
|
13
|
+
- [Compatibility Policy](compatibility-policy.md)
|
|
14
|
+
- [Manifest Schema](manifest-schema.md)
|
|
15
|
+
- [Verify Compat](verify-compat.md)
|
|
11
16
|
|
|
12
17
|
Read the quickstart first. The goal is to get from a spec to generated files without learning compatibility overlays, extractors, or smoke workflows.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Manifest Schema
|
|
2
|
+
|
|
3
|
+
`.oagen-manifest.json` is a generated provenance artifact. It records what was generated, from which inputs, and when. It is machine-owned — humans should not edit it.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
1. **Pruning** — On regeneration, oagen deletes files from the previous manifest that are no longer emitted, preventing stale-file accumulation
|
|
8
|
+
2. **Provenance** — Records the spec, emitter, and config used for each generation run
|
|
9
|
+
3. **Compat versioning** — Records the compat schema version for snapshot compatibility
|
|
10
|
+
|
|
11
|
+
## Schema (v2)
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"version": 2,
|
|
16
|
+
"language": "php",
|
|
17
|
+
"sdkName": "workos-php",
|
|
18
|
+
"generatedAt": "2026-04-18T22:02:14.077Z",
|
|
19
|
+
"specSha": "sha256:abc123...",
|
|
20
|
+
"specPath": "../openapi-spec/spec/open-api-spec.yaml",
|
|
21
|
+
"emitterSha": "def456",
|
|
22
|
+
"emitterVersion": "0.7.0",
|
|
23
|
+
"configSha": "sha256:789abc...",
|
|
24
|
+
"compatSchemaVersion": "1",
|
|
25
|
+
"files": ["lib/Resource/CreateUser.php", "lib/Service/UserManagement.php"]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Field Semantics
|
|
30
|
+
|
|
31
|
+
| Field | Type | Required | Description |
|
|
32
|
+
| --------------------- | ---------- | -------- | -------------------------------------------------------------------- |
|
|
33
|
+
| `version` | `number` | yes | Manifest schema version (currently `2`) |
|
|
34
|
+
| `language` | `string` | yes | Emitter target language (e.g., `"php"`) |
|
|
35
|
+
| `sdkName` | `string` | no | Human-readable SDK identity (e.g., `"workos-php"`) |
|
|
36
|
+
| `generatedAt` | `string` | yes | ISO-8601 timestamp of the generation run |
|
|
37
|
+
| `specSha` | `string` | no | SHA-256 hash of the source OpenAPI spec |
|
|
38
|
+
| `specPath` | `string` | no | Path or reference to the source spec |
|
|
39
|
+
| `emitterSha` | `string` | no | Git SHA of the emitter code used |
|
|
40
|
+
| `emitterVersion` | `string` | no | Emitter version string |
|
|
41
|
+
| `configSha` | `string` | no | SHA-256 hash of the effective `oagen.config.ts` |
|
|
42
|
+
| `compatSchemaVersion` | `string` | no | Version of the compat snapshot schema |
|
|
43
|
+
| `files` | `string[]` | yes | Sorted list of generated file paths (relative to manifest directory) |
|
|
44
|
+
|
|
45
|
+
## Generated Ownership
|
|
46
|
+
|
|
47
|
+
The manifest is generated by `oagen generate` and written to the output directory. It should be committed to the SDK repository for pruning to work across regenerations.
|
|
48
|
+
|
|
49
|
+
**Never put human-authored content in the manifest.** Approvals, allowlists, and policy belong in `oagen.config.ts`. The manifest is regenerated on every run — any manual edits will be overwritten.
|
|
50
|
+
|
|
51
|
+
## Pruning Semantics
|
|
52
|
+
|
|
53
|
+
When `oagen generate` runs:
|
|
54
|
+
|
|
55
|
+
1. Read the existing `.oagen-manifest.json` (if present)
|
|
56
|
+
2. Compare the previous `files` list against the current emission set
|
|
57
|
+
3. Delete files that were previously generated but are no longer emitted
|
|
58
|
+
4. Only delete files whose contents start with the oagen generation header (when a header guard is available)
|
|
59
|
+
5. Write a new manifest with the current file list
|
|
60
|
+
|
|
61
|
+
On first adoption (no previous manifest), pruning is skipped.
|
|
62
|
+
|
|
63
|
+
## Backward Compatibility
|
|
64
|
+
|
|
65
|
+
The manifest reader accepts both v1 and v2 manifests. v1 manifests (which lack `sdkName`, `specSha`, etc.) are read successfully — the new fields are simply absent. A v1 manifest written by an older oagen version will work with the current pruning system.
|
|
66
|
+
|
|
67
|
+
If the manifest version is higher than supported, it is ignored with a warning.
|
|
68
|
+
|
|
69
|
+
## What Must Not Go in the Manifest
|
|
70
|
+
|
|
71
|
+
- Approvals or allowlists (human-authored → `oagen.config.ts`)
|
|
72
|
+
- Release thresholds (human-authored → `oagen.config.ts`)
|
|
73
|
+
- Manual exception records
|
|
74
|
+
- Policy overrides
|
|
75
|
+
|
|
76
|
+
The rule: **never make the generator rewrite a file that humans edit for policy.**
|
package/docs/core/quickstart.md
CHANGED
|
@@ -35,11 +35,18 @@ npm install
|
|
|
35
35
|
|
|
36
36
|
The scaffold gives you:
|
|
37
37
|
|
|
38
|
-
- `oagen.config.ts`
|
|
39
|
-
- `src/
|
|
38
|
+
- `oagen.config.ts` -- a minimal config for local development that imports the plugin bundle
|
|
39
|
+
- `src/plugin.ts` -- plugin bundle export registering the stub emitter
|
|
40
|
+
- `src/demo/index.ts` -- stub emitter implementing the `Emitter` interface
|
|
40
41
|
- build/test tooling
|
|
41
42
|
- `sdk:generate` and related package scripts
|
|
42
43
|
|
|
44
|
+
> **Note:** The scaffolded project uses the plugin model. The consumer project
|
|
45
|
+
> (e.g. a spec repo) imports the plugin bundle from `src/plugin.ts` and layers
|
|
46
|
+
> its own spec interpretation policy on top. See the
|
|
47
|
+
> [CLI Reference](../cli.md#configuration-oagenconfigts) for plugin
|
|
48
|
+
> composition examples.
|
|
49
|
+
|
|
43
50
|
## 4. Replace the Stub Emitter
|
|
44
51
|
|
|
45
52
|
Edit `src/demo/index.ts` to return a few simple files:
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Verify Compat
|
|
2
|
+
|
|
3
|
+
`oagen verify` runs compatibility checks as part of its verification pipeline. This document covers the compat-specific CLI flags, report outputs, and CI integration.
|
|
4
|
+
|
|
5
|
+
## CLI Flags
|
|
6
|
+
|
|
7
|
+
### Existing Flags (Compat-Related)
|
|
8
|
+
|
|
9
|
+
| Flag | Description |
|
|
10
|
+
| ---------------------- | ------------------------------------------------------------------- |
|
|
11
|
+
| `--api-surface <path>` | Baseline API surface JSON — enables compat verification |
|
|
12
|
+
| `--spec <path>` | OpenAPI spec for spec-scoped comparison |
|
|
13
|
+
| `--scope <mode>` | `full` or `spec-only` (default: `spec-only` when `--spec` provided) |
|
|
14
|
+
| `--max-retries <n>` | Max overlay patch iterations (default: 3) |
|
|
15
|
+
| `--diagnostics` | Write `verify-diagnostics.json` |
|
|
16
|
+
|
|
17
|
+
### New Flags (Classified Compat)
|
|
18
|
+
|
|
19
|
+
| Flag | Description |
|
|
20
|
+
| -------------------------- | ------------------------------------------------------------ |
|
|
21
|
+
| `--compat-report <path>` | Write machine-readable classified compat report to this path |
|
|
22
|
+
| `--compat-fail-on <level>` | Override fail threshold: `none`, `breaking`, or `soft-risk` |
|
|
23
|
+
| `--compat-baseline <path>` | Path to baseline compatibility snapshot |
|
|
24
|
+
| `--compat-explain` | Include provenance explanations in terminal output |
|
|
25
|
+
|
|
26
|
+
CLI flags take precedence over `oagen.config.ts` settings.
|
|
27
|
+
|
|
28
|
+
## Report Outputs
|
|
29
|
+
|
|
30
|
+
### Machine-Readable Report (`--compat-report`)
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"schemaVersion": "1",
|
|
35
|
+
"language": "php",
|
|
36
|
+
"summary": {
|
|
37
|
+
"breaking": 3,
|
|
38
|
+
"softRisk": 2,
|
|
39
|
+
"additive": 11
|
|
40
|
+
},
|
|
41
|
+
"changes": [
|
|
42
|
+
{
|
|
43
|
+
"severity": "breaking",
|
|
44
|
+
"category": "parameter_renamed",
|
|
45
|
+
"symbol": "Authorization.check",
|
|
46
|
+
"conceptualChangeId": "chg_parameter_renamed_authorization.check_resourceid",
|
|
47
|
+
"provenance": "emitter_template_change",
|
|
48
|
+
"old": { "parameter": "resourceId" },
|
|
49
|
+
"new": { "parameter": "resourceTarget" }
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Human Summary (`--compat-explain`)
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Compat report for php:
|
|
59
|
+
3 breaking, 2 soft-risk, 11 additive
|
|
60
|
+
|
|
61
|
+
Breaking:
|
|
62
|
+
[parameter_renamed] Authorization.check — Parameter "resourceId" renamed to "resourceTarget"
|
|
63
|
+
provenance: emitter_template_change
|
|
64
|
+
[parameter_removed] UserManagement.createUser — Parameter "passwordHashType" removed
|
|
65
|
+
provenance: spec_shape_change
|
|
66
|
+
|
|
67
|
+
Soft-risk:
|
|
68
|
+
[constructor_reordered_named_friendly] CreateUser.constructor — reordered (named-friendly)
|
|
69
|
+
provenance: normalization_change
|
|
70
|
+
|
|
71
|
+
Additive: 11 new symbol(s)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Diagnostics (`--diagnostics`)
|
|
75
|
+
|
|
76
|
+
The existing `verify-diagnostics.json` continues to include legacy compat data (preservation score, violations by category/severity). The new classified report is written separately via `--compat-report`.
|
|
77
|
+
|
|
78
|
+
## Failure Thresholds
|
|
79
|
+
|
|
80
|
+
The verify command fails when unapproved changes meet or exceed the fail threshold:
|
|
81
|
+
|
|
82
|
+
| `--compat-fail-on` | Fails on |
|
|
83
|
+
| ------------------ | ---------------------------------------- |
|
|
84
|
+
| `none` | Never fails for compat changes |
|
|
85
|
+
| `breaking` | Unapproved breaking changes (default) |
|
|
86
|
+
| `soft-risk` | Unapproved breaking OR soft-risk changes |
|
|
87
|
+
|
|
88
|
+
Approved changes (matched by `compat.allow` in config) are excluded from the failure threshold.
|
|
89
|
+
|
|
90
|
+
## CI Integration
|
|
91
|
+
|
|
92
|
+
There are two CI models: **integrated** (via `oagen verify`) and **file-based** (via standalone `compat-extract` / `compat-diff` / `compat-summary` commands). The file-based model is recommended for new setups.
|
|
93
|
+
|
|
94
|
+
### File-Based CI (Recommended)
|
|
95
|
+
|
|
96
|
+
The file-based model uses `.oagen-compat-snapshot.json` as a committed baseline artifact. On each PR, the candidate SDK is extracted, diffed against the baseline, and a summary is posted as a PR comment.
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
name: Compat Check
|
|
100
|
+
on: [pull_request]
|
|
101
|
+
|
|
102
|
+
jobs:
|
|
103
|
+
compat:
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
strategy:
|
|
106
|
+
matrix:
|
|
107
|
+
lang: [node, php, python, ruby, go, kotlin, dotnet, elixir, rust]
|
|
108
|
+
steps:
|
|
109
|
+
- uses: actions/checkout@v4
|
|
110
|
+
|
|
111
|
+
- name: Generate candidate SDK
|
|
112
|
+
run: oagen generate --spec openapi.yml --lang ${{ matrix.lang }} --output ./generated
|
|
113
|
+
|
|
114
|
+
- name: Extract candidate snapshot
|
|
115
|
+
run: |
|
|
116
|
+
oagen compat-extract \
|
|
117
|
+
--sdk-path ./generated \
|
|
118
|
+
--lang ${{ matrix.lang }} \
|
|
119
|
+
--output /tmp \
|
|
120
|
+
--spec openapi.yml
|
|
121
|
+
|
|
122
|
+
- name: Diff against baseline
|
|
123
|
+
run: |
|
|
124
|
+
oagen compat-diff \
|
|
125
|
+
--baseline .oagen-compat-snapshot.json \
|
|
126
|
+
--candidate /tmp/.oagen-compat-snapshot.json \
|
|
127
|
+
--output compat-report-${{ matrix.lang }}.json \
|
|
128
|
+
--fail-on breaking
|
|
129
|
+
|
|
130
|
+
- uses: actions/upload-artifact@v4
|
|
131
|
+
if: always()
|
|
132
|
+
with:
|
|
133
|
+
name: compat-report-${{ matrix.lang }}
|
|
134
|
+
path: compat-report-${{ matrix.lang }}.json
|
|
135
|
+
|
|
136
|
+
summary:
|
|
137
|
+
needs: compat
|
|
138
|
+
if: always()
|
|
139
|
+
runs-on: ubuntu-latest
|
|
140
|
+
steps:
|
|
141
|
+
- uses: actions/checkout@v4
|
|
142
|
+
- uses: actions/download-artifact@v4
|
|
143
|
+
with:
|
|
144
|
+
pattern: compat-report-*
|
|
145
|
+
merge-multiple: true
|
|
146
|
+
|
|
147
|
+
- name: Post cross-language PR summary
|
|
148
|
+
run: |
|
|
149
|
+
oagen compat-summary \
|
|
150
|
+
--report compat-report-*.json \
|
|
151
|
+
| gh pr comment ${{ github.event.pull_request.number }} --body-file -
|
|
152
|
+
env:
|
|
153
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
For a single-language project, simplify to one job without the matrix:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
oagen compat-extract --sdk-path ./generated --lang node --output /tmp --spec openapi.yml
|
|
160
|
+
oagen compat-diff --baseline .oagen-compat-snapshot.json --candidate /tmp/.oagen-compat-snapshot.json --output report.json --fail-on breaking
|
|
161
|
+
oagen compat-summary --report report.json | gh pr comment --body-file -
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Baseline Management
|
|
165
|
+
|
|
166
|
+
The baseline snapshot is committed to the repository and updated as part of the release process:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# After releasing a new SDK version, update the baseline
|
|
170
|
+
oagen compat-extract --sdk-path . --lang node --output .
|
|
171
|
+
git add .oagen-compat-snapshot.json
|
|
172
|
+
git commit -m "chore: update compat baseline for v2.1.0"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### Report-Only Mode (Phase 4)
|
|
176
|
+
|
|
177
|
+
To run compat checks without blocking PRs, set `--fail-on none`:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
oagen compat-diff \
|
|
181
|
+
--baseline .oagen-compat-snapshot.json \
|
|
182
|
+
--candidate /tmp/candidate-snapshot.json \
|
|
183
|
+
--output compat-report.json \
|
|
184
|
+
--fail-on none
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The PR summary will still be posted, but the job will not fail. Switch to `--fail-on breaking` when ready for hard enforcement.
|
|
188
|
+
|
|
189
|
+
### Integrated CI (via `oagen verify`)
|
|
190
|
+
|
|
191
|
+
The `oagen verify` command runs compat checks as part of its verification pipeline. This is suitable when you want smoke tests and compat checks in a single step.
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
- name: Verify SDK
|
|
195
|
+
run: |
|
|
196
|
+
oagen verify \
|
|
197
|
+
--lang php \
|
|
198
|
+
--output ./generated \
|
|
199
|
+
--api-surface sdk-php-surface.json \
|
|
200
|
+
--spec openapi.yaml \
|
|
201
|
+
--compat-report compat-report.json \
|
|
202
|
+
--compat-fail-on breaking \
|
|
203
|
+
--diagnostics
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Exit Codes
|
|
207
|
+
|
|
208
|
+
| Code | Meaning |
|
|
209
|
+
| ---- | ---------------------------------------- |
|
|
210
|
+
| 0 | All checks passed |
|
|
211
|
+
| 1 | Compat violations or smoke test findings |
|
|
212
|
+
| 2 | SDK compile errors |
|
|
213
|
+
|
|
214
|
+
## How to Read and Act on Findings
|
|
215
|
+
|
|
216
|
+
### Breaking Changes
|
|
217
|
+
|
|
218
|
+
1. Check the `provenance` field — is this from a spec change or an emitter change?
|
|
219
|
+
2. If spec-driven: this may need a major version release
|
|
220
|
+
3. If emitter-driven: fix the emitter to preserve backward compatibility
|
|
221
|
+
4. If intentional: add an approval to `oagen.config.ts`
|
|
222
|
+
|
|
223
|
+
### Soft-Risk Changes
|
|
224
|
+
|
|
225
|
+
1. Review whether callers are likely affected
|
|
226
|
+
2. If safe: no action needed (or add approval for documentation)
|
|
227
|
+
3. If risky: fix the emitter or bump the release level
|
|
228
|
+
|
|
229
|
+
### Additive Changes
|
|
230
|
+
|
|
231
|
+
Safe to ship. No action needed.
|
|
232
|
+
|
|
233
|
+
## Overlay Retry Loop
|
|
234
|
+
|
|
235
|
+
The existing overlay retry loop (`--max-retries`) continues to work with the legacy compat system. It patches `public-api` and `export-structure` violations by correcting overlay mappings and regenerating. The classified compat report runs after the overlay loop has converged.
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ Scaffold a complete language emitter for oagen that translates the intermediate
|
|
|
11
11
|
|
|
12
12
|
oagen has a plugin architecture for code generation. Each target language is an **emitter** — a TypeScript module that implements the `Emitter` interface. An emitter receives parsed IR nodes (models, enums, services, etc.) and returns `GeneratedFile[]` — arrays of `{ path, content }` pairs. The engine orchestrator calls each emitter method, prepends a file header, and writes the results to disk.
|
|
13
13
|
|
|
14
|
-
Emitters live in **external projects** (not inside the oagen core repo). They import all oagen types from `@workos/oagen` and
|
|
14
|
+
Emitters live in **external projects** (not inside the oagen core repo). They import all oagen types from `@workos/oagen` and are exported through the emitter project's plugin bundle (e.g., `workosEmittersPlugin`), which the consumer project's `oagen.config.ts` imports.
|
|
15
15
|
|
|
16
16
|
## Reference Docs
|
|
17
17
|
|
|
@@ -41,7 +41,7 @@ Before starting, read and understand these oagen core types (all imported from `
|
|
|
41
41
|
- `planOperation`, `OperationPlan` — operation analysis helpers
|
|
42
42
|
- `toPascalCase`, `toSnakeCase`, `toCamelCase`, `toKebabCase`, `toUpperSnakeCase` — naming utilities
|
|
43
43
|
|
|
44
|
-
Also read the project's `
|
|
44
|
+
Also read the project's plugin bundle export (e.g., `src/plugin.ts`) to understand how emitters are registered.
|
|
45
45
|
|
|
46
46
|
If an `sdk_path` argument is provided, you MUST thoroughly study that SDK before proceeding to Step 1. The existing SDK's actual code — not generic conventions — drives every design decision.
|
|
47
47
|
|
|
@@ -227,17 +227,22 @@ export const {language}Emitter: Emitter = {
|
|
|
227
227
|
|
|
228
228
|
## Step 7: Register Emitter
|
|
229
229
|
|
|
230
|
-
Add the emitter to `
|
|
230
|
+
Add the emitter to the plugin bundle export (e.g., `src/plugin.ts`) and re-export from `src/index.ts`:
|
|
231
231
|
|
|
232
232
|
```typescript
|
|
233
|
-
//
|
|
234
|
-
import { {language}Emitter } from './
|
|
235
|
-
const
|
|
233
|
+
// src/plugin.ts
|
|
234
|
+
import { {language}Emitter } from './{language}/index.js';
|
|
235
|
+
export const workosEmittersPlugin = {
|
|
236
|
+
emitters: [/* existing, */ {language}Emitter],
|
|
237
|
+
// ...
|
|
238
|
+
};
|
|
236
239
|
|
|
237
240
|
// src/index.ts
|
|
238
241
|
export { {language}Emitter } from './{language}/index.js';
|
|
239
242
|
```
|
|
240
243
|
|
|
244
|
+
The consumer project's `oagen.config.ts` imports the plugin bundle, so the new emitter is automatically available.
|
|
245
|
+
|
|
241
246
|
## Step 8: Create Tests
|
|
242
247
|
|
|
243
248
|
Create test files under `test/{language}/` **in the emitter project**:
|
|
@@ -386,7 +391,7 @@ This skill produces, in the emitter project:
|
|
|
386
391
|
- `src/{language}/*.ts` — emitter generator files implementing the `Emitter` interface
|
|
387
392
|
- `test/{language}/*.ts` — unit tests for each generator
|
|
388
393
|
- `docs/sdk-architecture/{language}.md` — SDK design document
|
|
389
|
-
- Updated `
|
|
394
|
+
- Updated plugin bundle (`src/plugin.ts`) and `src/index.ts` with the new emitter registered
|
|
390
395
|
|
|
391
396
|
## Backwards Compatibility
|
|
392
397
|
|