Skip to content

finding: SCIMMemberReferenceSchema.type admits 'Group' (RFC 7643 §4.2) but stable @better-auth/scim rejects it twice over — declaration-vs-contract fork, latent (zero runtime consumers) #11820

Closed as not planned

Description

@os-warren

Filed unassigned — recording, not claiming. Surfaced by the #11697 field-level parity measurement (card comment 5399511853, executed probe against a real constructed @better-auth/scim@1.7.1 plugin); filed by the spec PM seat so the fork survives #11697's closure. Awaiting first-touch triage.

Measured

  • Ours: SCIMMemberReferenceSchema.type (packages/spec/src/identity/scim.zod.ts) admits 'User' | 'Group' — RFC 7643 §4.2 canonical values — and the nested-group case is pinned green (packages/spec/src/identity/scim.test.ts:380, "should support nested groups").
  • Stable upstream: @better-auth/scim@1.7.1 rejects 'Group' twice over — a zod refine (dist/index.mjs:22) and a BAD_REQUEST/invalidValue throw (:4594) — and its own discovery document advertises canonicalValues: ['User']. Probe case C (nested group) was REJECTED by the real endpoint.

So published spec surface (with a rendered docs page) advertises a capability the shipped implementation answers with a 400. The divergence is latent, not live: SCIMGroupSchema has zero runtime consumers repo-wide and in objectui (docs pages only) — nothing routes group payloads through packages/spec today, and no leg of #11632 does.

The fork (from the measurement report, verbatim options)

  • A — keep 'User' | 'Group': RFC-faithful and portable to any SCIM peer; cost is docs advertising what our own deployment refuses.
  • B — narrow to 'User': declared = enforced; a generated app is stopped at authoring time rather than by the IdP; cost is a knowing RFC 7643 §4.2 divergence plus retiring the pinned nested-group case. If taken, it is its own Clause-② card with the RFC divergence recorded — never a rider.
  • C — defer until a downstream leg first makes this schema load-bearing; that leg's direction is itself the answer to "declaration or contract".

Measurement's recommendation: C, with B if the maintainer wants it closed now (the make-AI-metadata-hard-to-get-wrong axis favours B; the zero-consumer reading and startup scope discipline favour C).

Refs: #11697 (the measurement, comment 5399511853) · #11632 (SCIM epic) · #3653 (question A = implement).

Blocked-by: #11632


Triage: closed not_planned — North Star full-board re-grade (2026-09-19)

Path: none. All 22 identity-auth checklist items were read and not one tests SCIM (control: 96 items mention sign-in/login, so the instrument is live); enterprise IdP provisioning is not a step on the measured journey, and 「登录与身份」 means the application's own sign-in. The card itself records zero runtime consumers.

Nothing is lost. Closing does not delete this issue — body and thread stay readable and reopening is free. Maintainer-confirmed disposition; ⛔ this seat closes nothing on its own authority.


Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 24, 2026
  2. claude commented on Aug 25, 2026

    @claude
    Contributor

    Concentrated triage batch: finding → pm:blocked + domain:spec, Task — declaration-vs-contract fork (spec admits RFC 7643 'Group', stable @better-auth/scim@1.7.1 rejects it twice over), latent with zero runtime consumers. The resolving fact is owned by the SCIM stable migration: whether the platform ships nested-group membership is decided there, so this card waits on it rather than forcing a premature narrowing.

    Blocked-by: #11632

    On unlock (the epic closes): re-measure probe case C against the migrated endpoint — if 'Group' is still rejected, narrowing SCIMMemberReferenceSchema.type is a Clause-② contract change and goes to the decision box with the probe reading; if the migration brought group membership, close this as resolved-by-upstream.


    Generated by Claude Code

  3. added
    domain:specDomain lane — seat card indexed by label:pm:seat
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 25, 2026
  4. added theissue type on Aug 25, 2026
  5. removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Sep 19, 2026
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

    domain:specDomain lane — seat card indexed by label:pm:seat

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions