Skip to content

Root barrel exports BadgeSchema and InvitationSchema silently pin the retired v1beta1 construct #1154

Description

@alexquincy

Summary

The package root re-exports ~15 OpenAPI construct documents. Almost all of them
carry the construct version in the identifier, so a consumer can see which
edition they are getting at the import site:

CategoryDefinitionV1Beta1OpenApiSchema        -> constructs/v1beta1/category/CategorySchema
ComponentDefinitionV1Beta2OpenApiSchema       -> constructs/v1beta2/component/ComponentSchema
DesignDefinitionV1Beta2OpenApiSchema          -> constructs/v1beta2/design/DesignSchema
RelationshipDefinitionV1Alpha3OpenApiSchema   -> constructs/v1alpha3/relationship/RelationshipSchema
InvitationDefinitionV1Beta2OpenApiSchema      -> constructs/v1beta2/invitation/InvitationSchema
...

Exactly two do not, and both silently resolve to v1beta1:

Root export Resolves to Newer construct exists Go model consumers bind
BadgeSchema constructs/v1beta1/badge/BadgeSchema v1beta2 models/v1beta2/badge
InvitationSchema constructs/v1beta1/invitation/InvitationSchema v1beta2, v1beta3 models/v1beta3/invitation

(Verified against @meshery/schemas@1.3.44 by deep-equality between each root
export and every file under dist/constructs/.)

Why it bites

In both cases the difference between the pinned edition and the current one is
the snake_case -> camelCase identifier rename, i.e. exactly the field names
a consumer addresses:

badge       v1beta1: id, org_id,   label, name, description, image_url,  created_at, ...
            v1beta2: id, orgId,    label, name, description, imageUrl,   createdAt,  ...

invitation  v1beta1: id, owner_id, is_default, org_id, expires_at, accepted_by, created_at, ...
            v1beta3: id, owner,    isDefault,  orgId,  expiresAt,  acceptedBy,  createdAt,  ...

The Go models these constructs generate are what providers actually bind, and
they are camelCase — models/v1beta2/badge.Badge is tagged json:"imageUrl" /
json:"orgId", models/v1beta3/invitation.Invitation is tagged json:"owner" /
json:"expiresAt". The generated RTK operations agree (createOrUpdateBadge's
body declares imageUrl, orgId).

So an unsuffixed export name reads as the schema for that construct while
pinning the retired edition, and a consumer that addresses the live camelCase
field names inherits the mismatch with no error anywhere: _.pick /
property lookups against the document return nothing, and the failure surfaces
only as a form field that never renders.

That is not hypothetical — it shipped:
layer5io/meshery-cloud#5917.
The Recognitions create/edit modal projected its RJSF schema out of the root
BadgeSchema while addressing imageUrl. The image field never rendered, and
because required still named imageUrl, RJSF refused every submit against a
field the user could not see: "Create Recognition" sent no request at all. The
consumer's field names were correct throughout; only the import source was wrong.

Suggested fix

Either is fine, and either one makes the mistake impossible rather than merely
reviewable:

  1. Rename to match every sibling — BadgeDefinitionV1Beta2OpenApiSchema,
    InvitationDefinitionV1Beta3OpenApiSchema — keeping the old names as
    deprecated aliases for a release; or
  2. Repoint the two unsuffixed exports at the current construct edition.

A packaging test asserting that no root export of a construct document is
version-ambiguous would keep it from recurring as new constructs are added.

Consumer-side note

meshery-cloud has repointed its badge consumer at the
constructs/v1beta2/badge/BadgeSchema subpath and added an architecture guard
(ui/__tests__/architecture/schema-construct-version-parity.test.ts) that
refuses any import of these two exports and asserts every construct the UI
imports matches the version its Go server binds. InvitationSchema is not
consumed from the barrel there — both consumers already use the v1beta3
subpath — so it is currently a latent trap rather than a live defect.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions