Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
fdf171a
docs(emitters): hold every variant an anyOf value admits
OmarAlJarrah Nov 2, 2026
6ad8bb6
docs(emitters): sweep the inclusive union through every rule it reaches
OmarAlJarrah Nov 2, 2026
56ef954
docs(emitters): make plan.Policy data and define its envelopes
OmarAlJarrah Nov 2, 2026
4350fde
Merge remote-tracking branch 'origin/docs/emitters-inclusive-unions' …
OmarAlJarrah Nov 2, 2026
6a3acaa
docs(emitters): layer runtime policy and read embedded policy
OmarAlJarrah Nov 2, 2026
5557ce9
docs(emitters): encode every enum member as its JSON kind
OmarAlJarrah Nov 2, 2026
624f08e
docs(emitters): state Kotlin's reserved sets for units
OmarAlJarrah Nov 2, 2026
8d3481e
docs(emitters): define what a generation keeps for the next one
OmarAlJarrah Nov 2, 2026
2a1112a
docs(emitters): design the docs emitter and its shared examples
OmarAlJarrah Nov 2, 2026
9238d63
docs(emitters): give refiners the resolved runtime policy
OmarAlJarrah Nov 2, 2026
996728b
docs(emitters): say the policy input is resolved per operation
OmarAlJarrah Nov 2, 2026
d6b6c6f
Merge remote-tracking branch 'origin/docs/emitters-policy-layers' int…
OmarAlJarrah Nov 2, 2026
dd3f822
Merge remote-tracking branch 'origin/docs/emitters-enum-value-types' …
OmarAlJarrah Nov 2, 2026
450d8a0
Merge remote-tracking branch 'origin/docs/emitters-kotlin-reserved-se…
OmarAlJarrah Nov 2, 2026
96d3dfa
docs(emitters): give refiners the prior generation
OmarAlJarrah Nov 2, 2026
2363af2
Merge remote-tracking branch 'origin/docs/emitters-generations' into …
OmarAlJarrah Nov 2, 2026
7e82355
docs(emitters): give refiners the samples and route tables
OmarAlJarrah Nov 2, 2026
15c0c5a
docs(emitters): verify a generation before, during and after it
OmarAlJarrah Nov 2, 2026
e80fae5
docs(emitters): put the oneOf match rule under recognition
OmarAlJarrah Nov 2, 2026
ef5b668
Merge remote-tracking branch 'origin/docs/emitters-policy-layers' int…
OmarAlJarrah Nov 2, 2026
0cd8e0f
Merge remote-tracking branch 'origin/docs/emitters-enum-value-types' …
OmarAlJarrah Nov 2, 2026
462d232
Merge remote-tracking branch 'origin/docs/emitters-kotlin-reserved-se…
OmarAlJarrah Nov 2, 2026
97ff6df
Merge remote-tracking branch 'origin/docs/emitters-generations' into …
OmarAlJarrah Nov 2, 2026
eb1ac14
Merge remote-tracking branch 'origin/docs/emitters-docs-artifacts' in…
OmarAlJarrah Nov 2, 2026
2b810e6
Merge remote-tracking branch 'origin/main' into docs/emitters-inclusi…
OmarAlJarrah Nov 2, 2026
52cacf0
Merge remote-tracking branch 'origin/docs/emitters-inclusive-unions' …
OmarAlJarrah Nov 2, 2026
ca779cd
Merge remote-tracking branch 'origin/docs/emitters-envelopes-policy-d…
OmarAlJarrah Nov 2, 2026
10a8149
Merge remote-tracking branch 'origin/docs/emitters-policy-layers' int…
OmarAlJarrah Nov 2, 2026
b41cce5
Merge remote-tracking branch 'origin/docs/emitters-enum-value-types' …
OmarAlJarrah Nov 2, 2026
91eeda7
Merge remote-tracking branch 'origin/docs/emitters-kotlin-reserved-se…
OmarAlJarrah Nov 2, 2026
2712385
Merge remote-tracking branch 'origin/docs/emitters-generations' into …
OmarAlJarrah Nov 2, 2026
3eb92c0
Merge remote-tracking branch 'origin/docs/emitters-docs-artifacts' in…
OmarAlJarrah Nov 2, 2026
80c9719
Merge remote-tracking branch 'origin/main' into docs/emitters-inclusi…
OmarAlJarrah Nov 2, 2026
fbdab78
Merge remote-tracking branch 'origin/docs/emitters-inclusive-unions' …
OmarAlJarrah Nov 2, 2026
f5137bc
Merge remote-tracking branch 'origin/docs/emitters-envelopes-policy-d…
OmarAlJarrah Nov 2, 2026
82570f3
Merge remote-tracking branch 'origin/docs/emitters-policy-layers' int…
OmarAlJarrah Nov 2, 2026
66d32c0
Merge remote-tracking branch 'origin/docs/emitters-enum-value-types' …
OmarAlJarrah Nov 2, 2026
8f65270
Merge remote-tracking branch 'origin/docs/emitters-kotlin-reserved-se…
OmarAlJarrah Nov 2, 2026
5cf8156
Merge remote-tracking branch 'origin/docs/emitters-generations' into …
OmarAlJarrah Nov 2, 2026
17c8a65
Merge remote-tracking branch 'origin/docs/emitters-docs-artifacts' in…
OmarAlJarrah Nov 2, 2026
2e42d54
Merge remote-tracking branch 'origin/main' into docs/emitters-inclusi…
OmarAlJarrah Nov 2, 2026
30379b6
Merge remote-tracking branch 'origin/docs/emitters-inclusive-unions' …
OmarAlJarrah Nov 2, 2026
4f453ed
Merge remote-tracking branch 'origin/docs/emitters-envelopes-policy-d…
OmarAlJarrah Nov 2, 2026
d54607f
Merge remote-tracking branch 'origin/docs/emitters-policy-layers' int…
OmarAlJarrah Nov 2, 2026
65cd8cb
Merge remote-tracking branch 'origin/docs/emitters-enum-value-types' …
OmarAlJarrah Nov 2, 2026
b60aedf
Merge remote-tracking branch 'origin/docs/emitters-kotlin-reserved-se…
OmarAlJarrah Nov 2, 2026
e07a5e5
Merge remote-tracking branch 'origin/docs/emitters-generations' into …
OmarAlJarrah Nov 2, 2026
fe61944
Merge remote-tracking branch 'origin/docs/emitters-docs-artifacts' in…
OmarAlJarrah Nov 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,10 @@ Passes operate on the IR only; they know nothing about source formats or target

### 2.3 Emitters (IR → artifacts)

Emitters are plugins (SDK-per-language, docs, mock servers…). Out of scope for this session
except for the boundary they impose on the IR; the internal shape mirrors what Kiota and oagen
converged on independently:
Emitters are plugins (SDK-per-language, docs, mock servers, and a command-line client planned as an
extension point, `docs/emitters/docs-and-mock.md`…). Out of scope for this session except for the
boundary they impose on the IR; the internal shape mirrors what Kiota and oagen converged on
independently:

1. **Plan** — compute language-*neutral* per-operation and per-type decisions once (is-paginated,
error taxonomy, primary-content/response selection, return-shape classification (a success body
Expand All @@ -415,8 +416,9 @@ though both are post-milestone scope:

- **Write/integrate** — regenerating into a live repo alongside hand-written code requires:
deterministic file paths, a generation manifest (spec/emitter/config hashes, sorted file
list, and an entity → generated-symbol map **keyed by IR stable IDs**, which survive path
and name churn), file-header provenance that gates pruning (never delete a file lacking the
list, and an entity → generated-symbol map **keyed by IR stable IDs**, which survive a rename
of what they are not built from, a correlation key carrying the rest (#679)), file-header
provenance that gates pruning (never delete a file lacking the
generated-by header), ignore-region markers for hand-written islands, and additive-only
merging. Additive-only writers also need a staleness check — (previous-revision entities −
current entities) ∩ files-on-disk — to surface dead code that pruning cannot touch.
Expand All @@ -442,10 +444,12 @@ logical exception-kind and fault, doc-URL template), **telemetry** (request-
ID and client-telemetry headers), **logging** (a closed lifecycle-event list), **user-agent**
construction (identifier template, app-info enrichment), **idempotency injection** (header name,
auto-generate rules), **pagination pacing** (auto-page delay), and **request guards** (option
keys that must not appear as params — misuse detection). Delivery model: canonical defaults +
deep-partial per-emitters/per-project overrides. Precedence rule: *declared IR facts win over
policy defaults* — `ErrorCase.Retryable/Fault` and `Operation.Idempotency` come from the spec;
policy fills in where the spec is silent, never the reverse.
keys that must not appear as params — misuse detection), widened by connection-error retries,
`Retry-After` and **redirects**. Delivery model: canonical defaults + deep-partial layers, the
policy a document embeds below the project's and the project's below each emitter's, each with
per-operation overrides (`docs/emitters/contract/runtime-policy.md`). Precedence rule: *declared
IR facts win over policy defaults* — `ErrorCase.Retryable/Fault` and `Operation.Idempotency` come
from the spec; policy fills in where the spec is silent, never the reverse.

### 2.5 Changing the IR

Expand Down
9 changes: 5 additions & 4 deletions docs/emitters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ its own, and answers every construct of the [lowering table](refine/lowering-tab
- [Target options](contract/options.md): strict, versioned, each default belonging to a version.
- [The type table](contract/type-table.md): which target type each IR leaf type becomes.
- [Runtime policy](contract/runtime-policy.md) and [shaping hints](contract/shaping-hints.md): the
two inputs that are not the IR.
two inputs that are not the IR, and [the policy a document embeds](contract/embedded-policy.md),
read into each as its lowest layer.

**The plan: language-neutral decisions, computed once**

Expand Down Expand Up @@ -96,7 +97,7 @@ its own, and answers every construct of the [lowering table](refine/lowering-tab
**Other artifacts, and how the work is held**

- [Docs and mock-server emitters](docs-and-mock.md), [write / integrate](write-integrate.md),
[surface verification](surface-verification.md).
[verifying a generation](verification.md), [surface verification](surface-verification.md).
- [Package layout](packages.md), [mandatory tests](testing/mandatory-tests.md), the [behavior
server](testing/behavior-server.md), the [milestone plan](milestones.md) and the [open
questions](open-questions.md).
Expand Down Expand Up @@ -234,11 +235,11 @@ The Kotlin targets read the same plan and lower the same facts as Go.
|---|---|
| 1 IR is the ABI | `EmitterInput.Doc` is the only structural input; `emitters/*` imports `ir`, `ir/irfacts` + shared contract, never `compiler`/`engine`/a sibling target ([the contract](contract/emitter.md), [package layout](packages.md)) |
| 2 Lossless, lowered late | all lowering is in `refine` steps building a fresh target AST; the IR is never mutated; unions/enums/discriminators/visibility/pagination/streaming/LRO arrive un-lowered and the step list is the acceptance test ([refine](refine/pipeline.md), [operations](operations/returns.md)) |
| 3 Stable IDs, names presentation | plan, AST `Origin`, manifest, surface, hints, and the collision table are all keyed by IR stable ID; renames are lookups; collisions never touch identity; a name with no words is named from its own characters alone, and placement reads no count, so neither moves when something else changes ([the plan](plan/overview.md), [naming](refine/naming.md), [output units](refine/output-units.md), [write / integrate](write-integrate.md)) |
| 3 Stable IDs, names presentation | plan, AST `Origin`, manifest, surface, hints, and the collision table are all keyed by IR stable ID; a rename that keeps an entity's pointer is a lookup, and one that moves it the correlation key's; collisions never touch identity; a name with no words is named from its own characters alone, and placement reads no count, so neither moves when something else changes ([the plan](plan/overview.md), [naming](refine/naming.md), [output units](refine/output-units.md), [write / integrate](write-integrate.md)) |
| 4 Names neutral, emitter cases | `Ident` starts as the `Naming.Canonical` word sequence, the source spelling beside each word for its letter case only; `RenderCasing`/`EscapeReservedWords` are the only casing steps; struct tags come from `WireName`/`WireID`, a separate channel ([naming](refine/naming.md), [emit](emit/printing.md)) |
| 5 Pure, typed diagnostics | `Generate` is pure/reentrant/no-stderr/no-I/O; every un-lowerable construct → coded `ir.Diagnostic`; the engine decides fatality ([the contract](contract/emitter.md), [contract rules](contract/emitter.md#contract-rules-the-type-system-and-tests-enforce)) |
| 6 Heuristics are policy | `plan.Policy`, each target's versioned options (its type table and naming tables among them), `verify.Severity`, method-name derivation, and shaping hints are injectable and disable-able; the type table's width-picking rows, the acronyms, the character names and the leading words are defaults a configuration replaces; inferred outputs marked `Inferred` ([target options](contract/options.md), [type table](contract/type-table.md), [plan policy](plan/policy.md), [naming](refine/naming.md), [shaping hints](contract/shaping-hints.md), [surface verification](surface-verification.md)) |
| 7 Serializable/deterministic | `Plan` + manifest are JSON-serializable, and `Facts` is rebuilt from the document; maps walked sorted, slices source order; identical input ⇒ identical bytes ([who computes the plan](contract/emitter.md#who-computes-the-plan), [the plan](plan/overview.md), [determinism](emit/printing.md#determinism-and-the-velocity-answer), [mandatory tests](testing/mandatory-tests.md)) |
| 8 Optionality ≠ nullability | `LowerOptionality` renders all four states plus protobuf `Presence` as a third axis; collapses are diagnosed ([optionality](refine/models.md#optionality--nullability--presence-inv8)) |
| 9 Capability surface designed against every format | every `ir.TypeKind` and every operation facet (pagination/streaming/LRO/one-way/messaging) has a lowering path or diagnosed degrade ([step list](refine/steps.md), [lowering table](refine/lowering-table.md)); docs/mock/validation are registry entries over the same `Doc + Plan`; a new language forces no IR or plan change ([docs and mocks](docs-and-mock.md), [M5](milestones.md)) |
| 10 Runtime policy separate | `policy.Policy` tree in `EmitterInput.Policy`, never on `ir.Document`; it parameterises only boilerplate; declared IR facts win over its defaults ([runtime policy](contract/runtime-policy.md)) |
| 10 Runtime policy separate | `policy.Policy` trees, resolved per operation, in `EmitterInput.Policy`, never on `ir.Document`; it parameterises only boilerplate; declared IR facts win over its defaults ([runtime policy](contract/runtime-policy.md)) |
80 changes: 80 additions & 0 deletions docs/emitters/contract/embedded-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Policy and hints a document embeds

A document often carries its SDK's configuration as vendor extensions: retries and timeouts at the
document and on an operation, the statuses that count as failures, method and parameter names, the
sub-client an operation belongs to, an operation exposed under a second name, a parameter every
call sends. The compiler keeps each verbatim, an `Unmodeled` entry of reason `vendor_extension`
([unmodeled](../../ir/unmodeled.md)), and left there every emitter would parse them its own way. So
one reader, `emitters/embed`, turns them into the two inputs they configure, [runtime
policy](runtime-policy.md) and [shaping hints](shaping-hints.md), each a layer below the project's.

```go
package embed

type Mapping struct {
Keys map[string]Target // an extension's key as Unmodeled holds it, such as
// "openapi:x-sdk-retries", → what it configures
Disabled bool // reads nothing
}

type Target struct {
Tree string // "policy" | "hint"
Path []string // the member path inside that tree's JSON form,
// such as ["retry", "maxAttempts"] or ["rename"]
}

type Layer struct {
Policy policy.Layer // the lowest layer of runtime policy
Hints map[emitters.EntityID]emitters.ShapeHint // the lowest layer of hints
}

func Read(doc *ir.Document, m Mapping) (Layer, []ir.Diagnostic)
```

The engine calls `Read` once per document, before it resolves the layers, so an emitter sees only
the resolved policy and hints and never an extension (INV1).

## What a key configures

A key configures the node holding it. A `policy` key on the document configures every operation,
and one on an operation configures that operation, `Layer.Policy.Ops` keyed by its `OpID`
([layers](runtime-policy.md#layers-and-operations)). A `hint` key configures the node's own ID, so
a key on a parameter renames that parameter, on an operation that method, and on a service that
client. A key a mapping names on any other node, such as a `policy` key on a property, configures
nothing and is reported.

The value is decoded into the field `Path` names, as the tree's own JSON form reads it, refusing an
unknown member. A value of the wrong shape is reported as a warning at the extension and never
coerced, since the document means something by the key that the field does not hold. The entry
stays in `Unmodeled` either way, as a promotion leaves it
([promotion](../../ir/unmodeled.md#promotion)), so a consumer that disagrees with the mapping reads
the entry instead.

## The mapping is a configuration

The reader runs by default, and its default mapping is empty. The compiler's promotion policy ships
keys whose reading is a convention independent of any generator, such as `x-deprecated-reason`
([`extension-promotion`](../../compilers/openapi.md#extension-promotion)). No key for a retry, a
timeout or an SDK method name has one: each is a generator's own spelling, and its value's shape is
that generator's. So a configuration names the keys its documents use and what each one sets, and a
key it does not name stays where it was.

A mapping reads a key as `Unmodeled` holds it, the format's prefix included, and only entries of
reason `vendor_extension`, whose key is the source's own spelling. That is the stability #429 asks
the IR to state for such keys. A key under any other reason, such as `no_ir_home`, names a gap the
IR may close, and a mapping naming one is refused.

## Precedence

The embedded layer sits below the project's and the emitter's, for policy
([layers](runtime-policy.md#layers-and-operations)) and for hints alike: what a document's author
embeds is a default the SDK's author can override, and the override is reported as an info
diagnostic, so the document's author can see it was overruled. A declared IR fact still beats every
layer ([precedence](runtime-policy.md#precedence--declared-ir-facts-win-hard-rule)).

## Measured on the golden spec

The golden spec (spec @319dc756, morphic @b206dc99) carries hints nearly everywhere: a method name
and a group name on 3,761 of its 3,763 operations, a name on 118 parameters and on 30 properties,
each under a generator's own key. It carries no retry or timeout key. A mapping reading those keys
gives each method the name its document chose, which an emitter would otherwise derive.
43 changes: 36 additions & 7 deletions docs/emitters/contract/emitter.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,19 @@ type EmitterInput struct {
Doc *ir.Document // the ABI — the ONLY structural input (INV1)
Facts *irfacts.Index // what Doc implies: subtypes, usage, recursion (docs/ir/derived-facts.md), indexed ONCE by the engine, shared with the plan (below)
Plan *plan.Plan // language-neutral decisions, computed ONCE by the engine (../plan/overview.md)
Policy policy.Policy // runtime/SDK behavior — a SEPARATE tree, never in the IR (INV10; runtime-policy.md)
Hints map[EntityID]ShapeHint// SDK-shaping hints keyed by IR stable ID (shaping-hints.md)
Policy policy.Resolved // runtime/SDK behavior per operation, its layers resolved —
// a SEPARATE tree, never in the IR (INV10; runtime-policy.md,
// embedded-policy.md)
Hints map[EntityID]ShapeHint// SDK-shaping hints keyed by IR stable ID, their layers resolved
// (shaping-hints.md)
Scope *Scope // nil = whole document; else "emit only these" — full Doc still
// consumed so global decisions stay byte-identical
Prior *PriorState // prior manifest + prior surface for write/integrate; nil first run
Samples map[TargetKey]map[ir.OpID]Sample // each SDK target's usage samples, for a docs target
// (../docs-and-mock.md#samples-come-from-the-sdk-emitters)
Routes map[string]RouteTable // every version snapshot's routes, keyed by version, for a
// target whose Capabilities.CrossVersion is set
// (../docs-and-mock.md#routes-and-links)
Options TargetOptions // the target's own typed options, decoded and validated before
// Generate runs, its type table and naming tables among them
// (options.md, type-table.md, ../refine/naming.md)
Expand All @@ -42,8 +50,24 @@ type EmitterOutput struct {
Diagnostics []ir.Diagnostic // typed, coded, provenance-bearing; engine decides fatality
Manifest *manifest.Manifest // entity→symbol map keyed by IR stable IDs (../write-integrate.md)
Surface *verify.Surface // neutral API-surface projection of the generated output (../surface-verification.md)
Samples map[ir.OpID]Sample // an SDK target's usage sample per operation, for the docs
}

// Span is the lines one top-level declaration of a file holds, 1-based and inclusive.
type Span struct {
From, To int
Origin EntityID
}

// Sample is one operation's call as an SDK target spells it.
type Sample struct {
Language string // the fence's language, such as "go" or "kotlin"
Code string // the call, its values the plan's examples
}

// RouteTable is one snapshot's routes: each entity's page path and anchor.
type RouteTable map[EntityID]string

// EntityID is the string form of any IR stable ID (TypeID, OpID, PropID, ServiceID, …).
// Every IR ID is a `type X string`, so one keyspace addresses them all.
type EntityID = string
Expand All @@ -57,8 +81,11 @@ type Artifact struct {
Header HeaderPlacement // where the generated-by provenance header goes (top/after-shebang/none)
SkipIfExists bool // scaffolding written once (config, .gitignore) — never overwritten
Overwrite bool // regenerate but preserve ignore-regions (default is additive)
Integrate *IntegrateHint // path-prefix stripping / tree-shake root for live-repo merge (../write-integrate.md)
Integrate *IntegrateHint // path-prefix stripping / tree-shake root for live-repo merge, and
// whether the file is merged as blocks (../write-integrate.md)
Origins []EntityID // IR IDs whose emission produced this file (drives ID-keyed prune)
Spans []Span // the lines each top-level declaration holds, by its Origin, so a
// toolchain's message maps to an entity (../verification.md)
}
```

Expand Down Expand Up @@ -97,10 +124,12 @@ the document, which determines it.

## Contract rules the type system and tests enforce

- **Purity / determinism.** No I/O, clock, randomness, or stderr inside `Generate`. Same
`(Doc, Plan, Policy, Hints, Scope, Options)`, under a context that stays live, ⇒ byte-identical
`Artifacts`; `Facts` is a function of `Doc` and adds nothing to the tuple. Maps are walked in
sorted-key order, slices in source order ([T-6](../testing/mandatory-tests.md)).
- **Purity / determinism.** No I/O, clock, randomness, or stderr inside `Generate`. Same `(Doc,
Plan, Policy, Hints, Scope, Prior, Options)`, under a context that stays live, ⇒ byte-identical
`Artifacts`, the prior manifest included since the names a generation shipped are kept
([naming](../refine/naming.md#collisions-and-the-names-a-generation-shipped)); `Facts` is a
function of `Doc` and adds nothing to the tuple. Maps are walked in sorted-key order, slices in
source order ([T-6](../testing/mandatory-tests.md)).
- **Diagnostics are the only input-failure channel.** A construct the emitter cannot lower emits
`ir.Diagnostic{Severity, Code:"emitters/<target>/…", Message, Provenance}` — never a silent
fallback, never a panic ([T-7](../testing/mandatory-tests.md)). Codes live in a stable
Expand Down
4 changes: 4 additions & 0 deletions docs/emitters/contract/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ type Capabilities struct {
LowersKinds []ir.TypeKind // the kinds this emitter has an emit path for
WriteIntegrate bool // supports additive merge into a live repo (../write-integrate.md)
SurfaceVerify bool // produces a verify.Surface (../surface-verification.md)
CrossVersion bool // reads every version snapshot's routes (../docs-and-mock.md)
Rows []string // the capability-matrix rows it honors, by key
// (../verification.md)
Verify Verify // how its output is built and checked (../verification.md)
Protocols []string // "http", "grpc", "message", "graphql", "otp"
Lifecycle Lifecycle // "hidden" | "experimental" | "stable" | "sunset"; required
Successor TargetKey // the target replacing a sunset one; "" when none does
Expand Down
Loading
Loading