Repository navigation
docs(emitters): lower redaction and stability tiers - #1340
Open
OmarAlJarrah wants to merge 37 commits into
Open
OmarAlJarrah wants to merge 37 commits into
OmarAlJarrah wants to merge 37 commits into
Conversation
The IR could say an operation was deprecated, and could not say how stable it is: a tier survived only as a verbatim Unmodeled entry, so each emitter would derive it from a vendor key its own way. A parameter had no redaction field at all, so `format: password` at a parameter stayed under Unmodeled while a property took Secret from the same schema. Operation and OperationGroup gain Stability, a closed alpha | beta | stable that is empty where a source states none, and irverify reports a tier the IR does not declare. Parameter gains Secret, the per-use form Property.Secret already is: a parameter never hoists a node, so a Sensitive scalar would have to be minted for it. The OpenAPI compiler now lowers `format: password` at a parameter that owns no node to Parameter.Secret, inherited from a $ref target as at a property, and keeps no openapi:format entry beside it. docs/ir/boundaries.md#access states the rule that separates TypeCommon.Access from client-shaping `@access`. BREAKING CHANGE: IRVersion moves to 0.23.0. A consumer pinned to 0.22.0 cannot decode a document that sets either field, and one reading openapi:format on a parameter no longer finds it.
Two keys mapped to one field resolved by how the keys sort, and nothing was reported: the policy was a map from key to target, which could not state an order, so the walk sorted the keys and the last one won. Targets is now a map from target to an ordered list of sources, and the list is the precedence: the first source stating a value fills the field, and a later one stating another is reported at its own key. A source can name a member of an object value, so one key that holds two facts can fill two fields. Reading moves off Ctx to a Promoter holding the policy. Ctx was one method short of the cap on a type's methods, and promotion reads nothing of it but the policy. The policy's lists are copied with the map, which maps.Clone alone left shared with the caller. BREAKING CHANGE: ExtensionPromotions.Targets changes from map[string]ExtensionTarget to map[ExtensionTarget][]ExtensionSource, and DefaultExtensionPromotions returns the new shape.
An extension stating a tier, a redaction or a deprecation reached only a verbatim Unmodeled entry, so an operation deprecated by a vendor key alone read as live, and a value marked sensitive read as plain. Three targets join the promotion policy. TargetDeprecation creates the Deprecation the member targets fill, at every carrier a keyword can. TargetStability fills Operation.Stability from a closed set of words. TargetRedaction sets Sensitive on a node and Secret on a property, header or parameter, where `format: password` lands. The default mapping gains the keys the golden spec writes for each, and for a deprecation's message and removal date, two of them objects read by member. A key mapped to both deprecation and stability reports one defect once: the word `deprecated` and a value neither can hold are the deprecation reader's to answer for.
Secret folds by OR, so a redeclared field takes it from a later declaration as readily as from the first, while the field keeps the first declaration's provenance. A redaction a promotion read on a later declaration would then carry no marker saying a heuristic made it, which the promotion contract requires of every node it writes. The fold joins the marker of each redacting declaration, which gives the same answer in either branch order.
Annotations merge from a $ref's target onto the property, parameter or
header naming it, field by field, but a promotion read only the node it
ran on. So `Old: {deprecated: true, x-sunset: D}` kept its date on the
node while `{$ref: Old}` lowered to an empty Deprecation, and a key that
deprecates a target left its uses live, though an allOf decorator over
the same target reaches the emitter deprecated through the fold.
A carrier now reads the target's mapped keys beneath its own. A key on
the target counts as the target's `deprecated`, so a `deprecated` beside
the $ref decides over it as it does over the keyword. Only the keys the
policy maps are looked up, and the target reports its own misfits, so a
use reports nothing a declaration already has.
Promotion moves to the caller of each carrier, after the object around a
header's or parameter's schema has spoken: that object decides what is
deprecated, and so what a member can fill.
A key on an allOf branch, on a path item, a tag or a content entry, or
inside a oneOf kept verbatim is still read by nothing, since a promotion
reads the top level of a carrier's Unmodeled. The rule's Status says so,
with the issues that track each.
A use read its target's mapped keys afresh, so a key's value was read once per use. A value as large as the document times as many uses as the document names it is their product: 84 KB with a 20,000 item key and 500 uses retired 58G instructions where main takes 0.9G. What a target lends is now read once per target and shared by every copy of the context, which puts the promoter behind one pointer. Three readings change with it. A target lends a member only where it is deprecated itself. A date on a live target annotates nothing there, and a use deprecated on its own read it anyway, while a 3.0 decorator over the same target could not: the fold that stands for it reads the Deprecation the node holds, and the node has none. The two spellings of one use now agree on what the target lends. Empty text states nothing. A field holding "" reads as unset, so such a key filled nothing, left a marker saying it had, and stood in front of a later source with something to say. Where the two stability keys disagree the order decides and not the tiers, which is what the code did and the text did not say. That it reads the less stable statement holds of what the golden spec writes.
Secret merges from a $ref target onto a parameter as it does onto a property, so the three comments listing what a use site holds of its target were one short, and the one in ir/typeref.go says everything else is read from the target. A schema between a use and the end of its chain lends nothing because that is what the keyword does, which #1029 tracks as a defect, so the rule cites it rather than settling it. And a key written on both a parameter or a header and its schema shares one entry (#396), so the schema's is not read. That is a limit on the rule that false clears no redaction, and it belongs where the rule is.
A limitation belongs at the code or rule that has it, and these sat in the PR text or nowhere: a key a parameter and its schema both write (#396), a stability key's boolean (#654), the facts the default leaves (#656), a key inside a verbatim oneOf (#661), a target read past the schema a use names (#1029), and what no refine step reads yet (#895, #1222). The use-site rule gains the Status it lacked, and the test holding a decorator to its $ref says which two spellings it does not cover, one of them newly filed as #1258. The cost test stops quoting a size it never measured. The promoter holds a memo now, so its comment says what makes that safe.
What a target lends is remembered by position, and two documents can hold one pointer, so the use read second must not take the first one's answer. No promotion test read a target in another document.
A parameter was said never to hoist a node of its own, though a schema with an enum does, and the sentence went on to count the parameters that own one. What holds is that a carrier never hoists an alias, so a parameter whose schema owns no node has none to hold a Sensitive.
The fold joins the marker of a later redaction, but the rule said only that a merged field names the heuristic behind its deprecation, and its pin was not cited. Name both. The version history records what each bump changed, and a lowering that starts filling a member is a change of meaning the shape cannot show. This bump has one: keys now state a deprecation and a redaction, and a use reads its target's. Record it beside the two shape changes. The list in ir.Constraints of what a $ref's use reads was the last of its kind to omit redaction.
The conformance assertion walks parameters, headers and properties but said two carriers, and the file comment above the promotion tests still called the reading one function and every target a field with no other spelling, though a Promoter holds it and `deprecated` is a keyword.
The comment on ExtensionTarget gives a silent policy as the reason the vocabulary is closed, while a mapping naming an unknown target compiles with no report. That predates this change and is filed as #1281; say so at the type and in the rule's status, so neither reads as enforced.
A parameter and a header elect their schema from `schema` or `content`, and PromoteUse takes the elected one. Every use-site case wrote `schema`, so reading the object's own schema instead left all of them green. Run each case at both spellings.
#1191 landed 0.23.0 first, so this change keeps the row and the history entry main holds and appends its own under 0.24.0. Its history entry now names 0.23.0 as the generation a pinned consumer holds.
Two promotion sites had no test that a wrong answer would turn red. Promoter.Stability joins its marker onto an operation's Inferred list. Assigning it instead dropped the streaming and grouping markers on the ten golden-spec operations that state a tier and stream by media type, so an inferred stream read as declared, with the suite green. The marker-list test now states a tier as well as a deprecation reason. A parameter or header whose content map holds several entries promotes from the entry the election takes, the lowest key. Promoting from the first declared entry instead left every test green at both carriers. The new test declares text/plain first and application/json second, with the target on each in turn.
#1219 took 0.24.0 and #1226 0.25.0, so this change moves to 0.26.0, keeps both rows and history entries, and appends its own. #1219 turned the field list of docs/ir/unmodeled.md#promotion into a table held to the code both ways. It gains the fields this change fills: a Deprecation itself at each carrier, Operation.Stability and Parameter.Secret, with TypeCommon named among the nodes keeping a deprecation's or a Secret's extension, as a $ref's target lends both to a use. TestExtensionTarget_EveryTargetIsInThePromotionTable now names every field a target fills rather than one, since TargetRedaction fills three. OperationGroup joins Variant and EnumMember as a node rule 4 names, and TestPromotionTable_EveryNodeCanRecord holds it there. #1219's rule 3 records a promotion on the node keeping the extension. A field a $ref's target lends a use is the one case this change records twice: the target keeps the entry and its marker, and the use lists the marker too, since a use is read without resolving its target and its type reaches the entry. Rule 3, extension-promotion and the 0.26.0 history entry say so. #1217 ended fillPropertyAnnotations in PreserveCarriedKeywords; the promotion this change moved to PromoteUse stays out of it. #1226 rewrote lowerSecurityScheme's return, which now calls the Promoter. The two new conformance goldens gain the unresolved auth namespace every OpenAPI document declares since #1226.
#1302 took 0.26.0 for a change of meaning, with the shape of 0.25.0, so this change moves to 0.27.0 and keeps its shape hash. Its history entry now names 0.26.0 as the version an older consumer is pinned to.
An allOf fold took a later declaration's whole marker whenever that declaration was secret. A property's marker covers every fact a promotion read into it, so a declaration redacted by format: password whose promoted removal date lost to an earlier deprecation marked the field, which then kept neither promoted fact. Each declaration now tells the merge which heuristics set its Secret, empty where a keyword declared it, and the fold joins that alone.
The fold's marker was held only from e2e, which the coverage gate does not count for compilers/openapi/internal/schema, so the line naming a promoted Secret's heuristic stood uncovered. The case moves beside the other allOf redeclaration tests, where both of its sites redden under a mutant.
A message #1219 wrote on main named PromoteDeprecation, which this change replaced with Promoter.Deprecation, and the merge kept it.
A use a $ref's target lends a promoted fact to lists the marker, though the entry the heuristic read is the target's, not its own. The field's definition named only the node's own Unmodeled.
No refine step read TypeCommon.Sensitive, so an emitter following the design redacted by Property.Secret alone and printed in the clear every value the IR marks through its type, and no document said what a target does with an operation's stability tier. The plan now records, per type, whether it is sensitive along its Base chain and which flattened members are redacted, accumulating Secret over every declaration of a member as requiredness does. A redacted value shows as [REDACTED] in every text rendering, a collection as the marker alone, and a raw body holding one is never logged; the wire is untouched. OpPlan.Stability resolves an operation's tier through its groups once. Every target documents an alpha or beta tier, Kotlin also requires an opt-in, and nothing reads an unstated tier as stable.
checkStability was shown every value of a document, though its type sits only on Operation and OperationGroup. That cost a verified compile of the golden spec about 0.5%, and Verify alone on its IR about 4% (spec @319dc756). It now prunes to the path checkStreamingModes already prunes to, renamed operationPath as it serves both, and TestOperationPath_ListsEveryCarrier counts Stability's carriers, so a field of that type off the path fails there rather than passing unread.
readStability's comment called a key's boolean the deprecation target's statement, which reads as both values. Only true is: a false names no tier and is reported here, as the extension-promotion rule states and TestPromoter_OneKeyTwoTargetsReportsOnce holds.
This was referenced Nov 2, 2026
4 of 5 tasks
The plan read a member's declarations itself because the flattening dropped a redaction an overridden declaration asked for. #1355 gives ir.FlatProperty a Secret that accumulates as Required does, so TypePlan.Redacted reads that instead.
…into docs/emitters-redaction-stability
…on-stability # Conflicts: # docs/ir/operations.md
7 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two facts the IR states that no document told an emitter what to do with. Both concern what an SDK shows rather than what it sends, so one document answers them (
docs/emitters/refine/redaction.md). This builds on #1229, now onmain, which givesParameteraSecretand operations aStability.Redaction (#895).
Property.Secretalone, the one example intargets/go/models.md. It would print in the clear every value the IR marks through its type: a property whose type is a sensitive component, a list offormat: passwordstrings, a parameter or header of a sensitive type.TypePlan.Sensitive(the type, or one itsBasechain reaches, throughAlias) andTypePlan.Redacted, the flattened members to redact. A member is redacted when any of its declarations asks, anOverriddenone included, accumulating as requiredness does. The plan reads that offir.FlatProperty.Secret, which fix(ir): keep the redaction an overridden declaration asks for #1355 adds to close ir: a flattened member drops a redaction an overridden declaration asks for #1211 in the IR's own flattening, so a later conjunct redeclaring a field withoutSecretcannot unredact it. That field lands with fix(ir): keep the redaction an overridden declaration asks for #1355, which sits on a different stack. The text here names a fieldmainholds only once fix(ir): keep the redaction an overridden declaration asks for #1355 lands, so merge fix(ir): keep the redaction an overridden declaration asks for #1355 before this PR, or accept the forward reference for the time between the two.Parameter.Secretor its type, as the issue's comment noted feat(ir)!: type the secrecy and stability a document states #1229 makes possible.[REDACTED]: string forms, logging events, a URL path segment or query value, header values, error messages;LowerRedactionjoins the step list. The GoUserexample and Go's type-scope reserved names now state both sources andGoString.Stability (#1222).
OpPlan.Stabilityresolves the tier once: the operation's own, else its nearest group's, else none. An unstated tier is never read asstable.alphaorbetaoperation's documentation names its tier, and the docs badge every stated tier.@AlphaApi(RequiresOptInatERROR) and@BetaApi(WARNING), with the tier also in KDoc, since Swift sees no annotation.alphaoperation out, since hiding operations is the filter pass's job, so the SDK and its docs hold the same ones.MarkStabilityjoins the step list, both lowering tables answer the new rows, anddocs/ir/operations.mdnow points here instead of at this issue.On the golden spec (spec @319dc756) 3,190 of 3,763 operations state a tier, as #1229's own document records.
T-45 pins redaction and T-46 stability.
Closes #895
Closes #1222
Test plan
docs/ir/operations.md#stabilityamong them.TestEmitterDocs_EveryTargetAnswersEveryConstruct's rule: each new shared row has an answer in both target tables, read by eye against the test's parser (first cell, non-empty rest).Merge notes. #1229 has landed, and this now targets
main. Its merge of #1229's final head and ofmainresolved one conflict, indocs/ir/operations.md: this PR's link replaces #1229's "not stated yet (#1222)". The design note there that cited #895 now links the plan's redaction reading. It is independent of the emitter-design stack (#1329 ← … ← #1339), which builds onmain. Whichever lands second resolves three small overlaps:testing/mandatory-tests.md(T-45 and T-46 here, after the stack's T-44);plan/plan-or-refine.md;AlphaApiandBetaApi.The insertions here avoid every other line the stack edits.