Skip to content

irverify: an operation, service or group ID that lost its separator verifies clean #802

Closed

Description

@OmarAlJarrah

Problem

An ID is <kind>/<namespace>/<path>, and the first two separators are all that say where the namespace ends. A producer that glues the path onto the namespace emits an ID that is well-formed in a namespace named for the concatenation:

op/openapi/paths/~1pets/get        what the producer means
op/openapipaths/~1pets/get         separator lost: namespace "openapipaths", path "~1pets/get"

irverify holds IDs to the grammar in two ways, and neither reaches this for every class:

  • Shape (ir/id-malformed) cannot see it, because the result is well-formed.
  • Agreement of the path with the pointer the node records (ir/id-provenance-disagreement) does see it, but only where the node records the pointer its ID was derived from: types, security schemes and properties.
    • An operation's provenance records where its body is declared and its ID where it is mounted, and the two differ for an operation reached through a $ref'd path item (component-reuse.yaml in the corpus).
    • A service records no pointer.
    • A group has no provenance, and is minted by a grouping rule rather than found at a coordinate.

So for operations, services and groups a lost separator verifies clean, and no check on the finished ID could see it.

Inside this repository a compiler cannot produce one. The framework supplies the separator when the namespace and the path arrive as separate arguments, and an architecture test holds compilers to building a namespace only from a string literal (added with operation-group IDs, #801). What is left is any document whose producer is not one of this repository's compilers: a decoded JSON document, a pass, an out-of-tree compiler. The IR is the ABI, so those are in scope.

Proposal

Let a document declare the namespaces its IDs use, and have irverify check membership.

  • Document gains a member holding the set of namespaces. A producer states it the way it already stamps IRVersion.
  • irverify reports an ID whose namespace (ir.IDSpace) is not in the set, once per ID, at the node. The shared primitive namespace is owned by ir (IDSpacePrim) and needs no declaration.
  • The check applies to every class that has a kind prefix, so it also covers types and security schemes, and it needs no format knowledge and no recorded pointer.
  • Each compiler builds the set from the namespace constants it already has. A test per compiler holds the declared set equal to the set its corpus actually uses, so it cannot drift in either direction: an undeclared real namespace would be a false violation, and an unused declared one a loophole.
  • IRVersion moves, per ir-design.md §2.1, since every document gains a key.

Open points

  1. A flat set or one per kind. A flat set is the smaller contract and is enough for a lost separator, which creates a namespace that exists nowhere. A set per kind would also catch an ID using another kind's namespace.
  2. Whether a declared namespace that no ID uses is reported. Reporting it closes the loophole, at the price of a rule every producer must keep in step.

Alternatives considered

  • Record on Provenance the pointer an ID was derived from when it differs from where the node is declared. Format-specific, widens every Provenance, and does nothing for services or groups, which derive from no pointer.
  • An unambiguous delimiter between the namespace and the path. Makes the defect structural, but rewrites every ID in every document, golden and test. Far larger than the problem.
  • Flag a namespace that extends another in use (anonaddr beside anon). A heuristic with false positives, because legitimate namespaces can share a prefix.
  • Do nothing. The protection then stays at the source, which is sufficient while every producer is in this repository.

When to do it

It protects only against producers outside this repository, and there are none yet. It becomes worth doing when the first out-of-tree compiler, or an external producer of IR JSON, appears.

Acceptance

  • A document with an operation, service, group, property, type or security-scheme ID in an undeclared namespace is reported by irverify, one violation per ID, at the node.
  • The primitive namespace needs no declaration.
  • The OpenAPI compiler declares its namespaces, and a test fails when the declared set and the set its conformance corpus uses differ in either direction.
  • ir-design.md records the member, the check and the version-history entry, and the sentence in §3.1 saying irverify cannot see a lost separator is removed.
  • Regenerating the goldens changes only the version line and the new member.

Activity

  1. OmarAlJarrah commented on Oct 9, 2026

    @OmarAlJarrah
    MemberAuthor

    Implemented in #801, which closes this on merge. The two open points were settled as follows.

    1. Flat set or one per kind. One per kind. A flat set cannot tell a namespace some other kind declares from one this kind may use, and the declaration is small enough that the extra precision costs nothing.
    2. A declared namespace that no ID uses. Not an error in a document: the declaration is a vocabulary, so a document without webhooks still lists the namespace its compiler would mint them in. The compiler's own test holds its list to what its corpus uses, in both directions, which is what closes the loophole.

    Decided in the same change: the declaration is required, like irVersion, and a document declaring nothing while carrying IDs is reported once and not per ID. It rides the 0.7.0 bump that adding OperationGroup.ID already makes, so it costs no further generation.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions