Skip to content

docs(emitters): layer runtime policy and read embedded policy - #1332

Open
OmarAlJarrah wants to merge 7 commits into
docs/emitters-envelopes-policy-datafrom
docs/emitters-policy-layers
Open

OmarAlJarrah wants to merge 7 commits into
docs/emitters-envelopes-policy-datafrom
docs/emitters-policy-layers

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Summary

Five gaps in what configures a generated SDK, resolved as one contract: who may set a behavior, at which scope, and from where.

Layers and per-operation policy (#710). Every source of runtime policy is now a policy.Layer, holding a document-wide override and overrides keyed by OpID. The layers, lowest first: the policy a document embeds, the project's, the emitter's. policy.Resolve gives EmitterInput.Policy as a policy.Resolved per operation.

  • A setting takes the highest layer that sets it, and within that layer an operation's value over the document-wide one. The issue proposed a single order mixing scope and source (fact, per-operation, project, defaults). That fails once a document embeds settings: its per-operation retries would outvote a project's choice to turn retries off, and the project could not win without naming every operation.
  • Declared IR facts still beat every layer.
  • An OpID matching nothing is reported, and so is an override of an embedded value, as an info.
  • T-9 now covers all of this.

Embedded policy and hints (#712) (docs/emitters/contract/embedded-policy.md). A new emitters/embed reader turns the vendor extensions a mapping names into the lowest layer of runtime policy and of hints.

  • A key configures the node holding it. A value is decoded strictly into the field the mapping names, and a wrong shape is reported, never coerced. The entry stays in Unmodeled, as a promotion leaves it.
  • The mapping is a configuration, so the reader runs by default with an empty default mapping. This departs from the issue's "on by default": no retry, timeout or SDK-name key has a meaning independent of one generator, unlike the compiler's deprecation keys.
  • It reads only vendor_extension entries, whose keys are the source's own, which is what Unmodeled key names are an unversioned contract that consumers are currently forced to read #429 asks the IR to state as stable. A mapping naming a key of another reason is refused.
  • ShapeHint.AlsoMount answers an operation exposed under several sub-clients, which one Remount could not. Parameters were already ID-keyed (ir: parameters are bound by name, so same-named parameters in different locations are ambiguous #596).
  • The golden spec carries a method and group name on 3,761 of its 3,763 operations, 118 parameter names and 30 property names, all under one generator's keys, and no retry or timeout key.

Missing behaviors (#711).

  • Retry gains Connection: a request that got no response is retried only for an idempotent operation by default. It also gains RetryAfter, honored by default and capped at Backoff.Max, and Throttled, the throttling backoff failures already named.
  • Redirects follows RFC 9110 §15.4 and never forwards credentials to another origin.
  • Decoding strictness and union strategy are deliberately not runtime policy: they change types or decoders, which runtime policy never shapes, and docs and mocks must agree with them. So plan.Policy gains OpenEnums (an enum a client reads keeps a value it does not list, as OpenUnions does for variants) and ExclusiveMatch (first or only for a oneOf value two variants admit). Unknown members follow each model's declared Additional, with no setting.
  • OpenEnums does not override the declared Enum.Closed. Closed states which members the document lists, and the SDK sends only those. It says nothing of what a later API version lists, which is what a client must survive. All 8,314 golden enums are closed.
  • A setting a target cannot honor is a coded diagnostic, never a quiet stricter behavior.

Effects from the method (#696). OpPlan.Effects{Safe, Idempotent} comes from the declared Operation.Idempotency (its token kind counts as idempotent), else from the method under plan.Policy.MethodSemantics, as RFC 9110 §9.2 defines it, marked Inferred. "Destructive" is stated as not derivable. The retry rule above reads Effects, and an idempotency key the runtime generates counts only where the IR declares its token. On the golden spec no operation declares idempotency, 1,841 use a safe method and 2,693 an idempotent one.

Client settings (#719) (docs/emitters/operations/client.md#client-settings).

  • A parameter becomes a client setting only when the service's ShapeHint.ClientParams names it by location and wire name, never by inference. Whether every operation of a scope shares a parameter changes as an API grows, and an inferred setting would move the parameter in or out of every method's signature, a break caused by an additive spec change.
  • A named setting fills each operation whose declaration lowers to one wire form (same scalar-chain root, style and explode), so a shared name alone never merges parameters. A different wire form stays an input and is reported.
  • A filled parameter becomes an optional field of the request type, so a call can still override it.
  • The plan reports candidates as infos. The issue's proposed test, one shared component parameter, was measured and is too narrow: it finds a parameter every operation of a group shares in 52 of 515 groups, against 465 by location and name, because the golden spec declares 10,550 parameters inline and 1,157 by component. account_id is a path parameter of 2,660 operations, all strings, under 16 distinct sets of constraints.

Measured at spec @319dc756, morphic @b206dc99.

Closes #696
Closes #710
Closes #711
Closes #712
Closes #719

Test plan

Stacked on #1331 (itself on #1329), because all three edit plan/policy.md, plan/types.md, refine/steps.md, docs-and-mock.md and the end of testing/mandatory-tests.md. Merge in order. After each lands, "Update branch" on the next is a merge of main that conflicts nowhere.

Runtime policy was one tree per project, so an operation needing its own
timeout or retry had nowhere to say so, and a document's own SDK
configuration, kept verbatim as vendor extensions, would have been
parsed by each emitter its own way.

Policy and hints are now layers, each holding a document-wide override
and overrides by operation: what a document embeds, the project's, the
emitter's. A setting takes the highest layer that sets it, an
operation's value over its layer's document-wide one, and a declared IR
fact beats every layer. A new emitters/embed reader turns vendor
extensions a configuration maps into the lowest layer of each, decoding
each value strictly and reporting what it cannot read.

The vocabulary gains connection-error retries, Retry-After, a throttling
backoff and redirects. Whether an operation is safe or idempotent is a
plan fact, OpPlan.Effects: declared, else from its HTTP method. An enum
a client reads keeps a value it does not list, as a union does, and a
oneOf value two variants admit decodes as plan.Policy.ExclusiveMatch
says. A service hint makes a parameter a client setting; nothing is
hoisted by inference, since a shared parameter stops being shared as an
API grows, and the plan only reports candidates.
EmitterInput.Policy became policy.Resolved when policy gained layers and
per-operation overrides, but the refiner context still held a single
policy.Policy, so a step had no way to read an operation's own settings.
Ctx.Policy now holds the resolved policy the emitter is given.
The rule saying what a oneOf decodes a value two variants admit to
landed at the end of the section on a variant's null, which it has
nothing to do with. It belongs with how a variant is recognized, which
is also where T-35 links it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant