Skip to content

openapi: a parameter and its schema share one Unmodeled namespace #396

Open

Description

@OmarAlJarrah

Summary

ir.Parameter.Unmodeled and the ir.Property.Unmodeled a header lowers to each
carry entries from two different source objects, both unscoped:

A parameter and its schema are two objects at two pointers, so both can spell the
same key. They then spell one entry, and which one survives is decided by
lowering order rather than by the document.

Reproduction

Verified at 6fcfd8a. Two shapes, both losing the parameter's own key:

# the schema's undeclared keyword takes the entry (#356 on both sides)
- name: p
  in: query
  divisibleBy: FROM_PARAMETER_OBJECT
  schema: {type: string, divisibleBy: FROM_SCHEMA}

# the keyword #348 keeps beside a $ref takes it
- name: p
  in: query
  format: FROM_PARAMETER_OBJECT
  schema: {$ref: '#/components/schemas/S', format: FROM_SCHEMA_BESIDE_REF}

Each compiles to a single openapi:divisibleBy / openapi:format on the
parameter, holding the schema's value. The same happens on a response header,
whose object keys and schema keywords share the ir.Property it lowers to.

Current state

#356 no longer drops the losing key in silence — it reports
openapi/unknown-key-entry-taken, naming the pointer of the construct holding
the entry. The key is still represented in the IR in no form at all, so this is a
report of the loss, not a fix for it.

The fix, and why it is not #356's

Applying the rule #345 already established for the objects that ride on a carrier
they do not own — key them by the source path from the carrier down — means the
schema's entries become openapi:schema/<keyword> on a parameter and a header,
while the object's own stay unscoped.

That is a one-line change per site and a wide change in effect: it moves keys
#348 and the validation-only reader already publish, so every golden carrying
an unhomed keyword or an if/not on a parameter or header is rewritten. It is
also a decision about the meaning of a carrier's namespace, which is worth
settling once for all three mechanisms rather than as a side effect of the census
that happened to surface it.

Properties are deliberately not in scope: a property is its schema, so its
carrier has only one namespace and its entries must stay unscoped.

Activity

  1. OmarAlJarrah commented on Sep 30, 2026

    @OmarAlJarrah
    MemberAuthor

    One more question this issue will have to answer: which value wins when a parameter and its schema carry the same key. Today the parameter's does, because its extensions are merged after the schema's (MergeUnmodeled is a plain overwrite):

    parameters:
      - {name: p, in: query, x-dup: FROM_PARAM, schema: {type: string, x-dup: FROM_SCHEMA}}

    keeps FROM_PARAM only, with no diagnostic. Keys that describe the value, such as a sensitivity flag or a field mapping, are more often written on the schema, so a precedence rule (or keeping both under separate namespaces, which is what this issue proposes) should say which one a consumer sees.

  2. added
    priority:P4Refactors, tests, CI, hostile-input hardening, cosmetic diagnostics, other formats
    on Oct 19, 2026
  3. OmarAlJarrah commented on Nov 1, 2026

    @OmarAlJarrah
    MemberAuthor

    #1229 makes this reach a typed field, in the opposite direction to the two shapes above. For an x-* key on both objects, annotation.MergeUnmodeled overwrites the schema's entry with the object's, in silence, and a promotion reads the entry that is left:

    - {name: a, in: query, x-sensitive: false, schema: {type: string, x-sensitive: true}}

    a compiles with no secret and one openapi:x-sensitive holding false, with nothing reported, though false states nothing elsewhere and clears no redaction. A header does the same, through applyHeaderAnnotations. The schema's true reaches the IR in no form, which it does on main too; what changes is that a typed field now rests on it. docs/compilers/openapi.md#extension-promotion names this in its Status.

    Off the golden spec: a walk of openapi.json over its 12,124 parameters and headers finds none writing a mapped key on both the object and its schema (spec @319dc756).

  4. OmarAlJarrah commented on Nov 2, 2026

    @OmarAlJarrah
    MemberAuthor

    One more carrier of two objects in one namespace once #1333 lands, which follows a content entry's $ref into components/mediaTypes. At a content-style parameter or header, the summary the fold takes from the use site or an alias is kept as openapi:content/<media type>/summary, and a summary the media type's declaration writes, which a Media Type Object does not define, is the census's under the same key.

    openapi: 3.2.0
    info: {title: T, version: "1"}
    paths:
      /a:
        get:
          operationId: a
          parameters:
            - name: q
              in: query
              content:
                application/json: {$ref: '#/components/mediaTypes/M', summary: The use.}
          responses:
            "200":
              description: ok
              headers:
                X-H:
                  content:
                    application/json: {$ref: '#/components/mediaTypes/M', summary: The use.}
              content:
                application/json: {$ref: '#/components/mediaTypes/M', summary: The use.}
    components:
      mediaTypes:
        M: {schema: {type: string}, summary: The declaration.}

    The parameter and the header each hold only The use., and openapi/unknown-key-entry-taken reports the declaration's summary at /components/mediaTypes/M/summary as represented in no form, once for each. The body keeps both: Content.Docs.Summary is The use., and Content.Unmodeled["openapi:summary"] holds The declaration.

    morphic @1afc54ee (the head of #1333).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority:P4Refactors, tests, CI, hostile-input hardening, cosmetic diagnostics, other formatstype:bugDefect in shipped behavior

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions