Skip to content

fix(enterprise): restore LEARNER lazy-create on invitation-accept (who-is-acting rule)#820

Merged
teetangh merged 5 commits into
feature/enterprisefrom
fix/learner-accept-lazy-create
Jun 10, 2026
Merged

fix(enterprise): restore LEARNER lazy-create on invitation-accept (who-is-acting rule)#820
teetangh merged 5 commits into
feature/enterprisefrom
fix/learner-accept-lazy-create

Conversation

@teetangh

@teetangh teetangh commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Context

fa769af added strict identity gates to both member-creation surfaces: EXPERT requires an existing ConsultantProfile, LEARNER an existing ConsulteeProfile. The EXPERT half implements #729's acceptance criteria and stays. The LEARNER half on invitation-accept broke the primary B2B onboarding path: lib/auth.ts deliberately removed signup-time ConsulteeProfile creation and names invite-accept as LEARNER as a sanctioned lazy-create trigger — with the gate, a sponsored employee who signed up via the invite link hit NOT_A_CONSULTEE on accept, with no self-service remedy short of personally booking a session first.

The rule (settled in #819)

Identity creation requires the user's own action; an admin acting on someone else requires pre-existing identity.

Surface EXPERT LEARNER
POST /members (admin direct-add) strict (unchanged) strict (unchanged)
invitation-accept (user's consenting click) strict (unchanged) lazy-create restored
SSO JIT auto-join lazy (unchanged) lazy (unchanged)

The asymmetry is principled: a consultant identity carries domain, rate, verification, and payout prerequisites that no invite click can substitute for, while the consumer profile is a lightweight record any user can self-mint by booking — gating it protects nothing and blocks onboarding.

Coverage

New source-pin tests assert the gates per surface (accept: NOT_A_CONSULTANT present, NOT_A_CONSULTEE absent; members POST: both present) and that the lib/auth.ts lazy-create sanction still stands — so the surfaces can't silently drift apart again. 23 tests across the three role suites pass; eslint clean; tsc --noEmit exit 0.

Closes #819.

🤖 Generated with Claude Code

…o-is-acting rule)

fa769af's strict identity gate on invitation-accept broke sponsored
onboarding: lib/auth.ts deliberately defers ConsulteeProfile creation
to the first consumer action and names invite-accept-as-LEARNER as a
sanctioned trigger, so a fresh employee accepting an org invite hit
NOT_A_CONSULTEE with no self-service remedy. Rule settled with #819:
identity creation requires the user's OWN action — accept keeps the
EXPERT strict gate (consultant identity has real prerequisites) but
lazy-creates the lightweight consumer profile; admin direct-add stays
strict for BOTH roles; SSO JIT unchanged. Source-pin tests stop the
gates drifting between surfaces.

Closes #819.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: df884051-d948-4c7e-bb83-4ccf80e72298

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/learner-accept-lazy-create

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request removes the strict NOT_A_CONSULTEE check from the invitation acceptance route, allowing ConsulteeProfile to be lazy-created for learners. It also introduces static source-code assertion tests to prevent gate constraints from drifting. Feedback on these changes highlights that reading and asserting on source files statically is a fragile testing pattern, and recommends using __dirname instead of process.cwd() to ensure robust path resolution across different environments.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +57 to +58
const read = (p: string) =>
readFileSync(join(process.cwd(), p), "utf8");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Asserting the presence of specific strings or comments in source files via readFileSync is a fragile testing pattern. It is highly sensitive to non-functional changes (such as code formatting, comment edits, or variable renaming) and does not validate actual runtime behavior.

Additionally, relying on process.cwd() can fail if tests are executed from a different working directory (e.g., in a monorepo or specific CI/CD environments). Using __dirname with relative paths is more robust for locating files relative to the test file itself.

Recommendation:
Consider replacing these static source-code checks with actual integration tests or mocked unit tests that verify the runtime behavior of the route handlers. If you must keep these source-level "pins", use __dirname to resolve the paths reliably.

Suggested change
const read = (p: string) =>
readFileSync(join(process.cwd(), p), "utf8");
const read = (p: string) =>
readFileSync(join(__dirname, "../../", p), "utf8");

@teetangh teetangh self-assigned this Jun 7, 2026
@teetangh teetangh requested a review from shubham79a June 7, 2026 16:35
teetangh and others added 3 commits June 8, 2026 08:43
…irname for path resolution

Modified the read function in the invitation-accept test to ensure it correctly resolves file paths relative to the test directory, improving test reliability across different working directories.
… site (#819)

The accept route explains why ITS LEARNER gate is absent; the members
route's gates never said why admin direct-add stays strict for both
roles. A future reader of this file alone now sees the full rule and
where the drift pins live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@teetangh

Copy link
Copy Markdown
Contributor Author

Assessment (pre-merge): logic verified sound and merged-state checked against the post-#822/#823/#824 feature/enterprise.

  • The asymmetry is principled and matches the rule recorded in 70c7d51: invitation-accept is the user's own consenting action, so the lightweight ConsulteeProfile lazy-creates there via applyMembershipRoleEffects (verified the helper's LEARNER branch does exactly this); EXPERT stays strict on every surface because the consultant identity carries domain/rate/verification/payout prerequisites; admin direct-add stays strict for both roles since that is an admin acting on someone else.
  • Verified gate placement on the merged tree: NOT_A_CONSULTEE absent from the accept route, both gates present in POST /members, and the lib/auth.ts sanction intact — the three new source pins now hold on the combined result.
  • Gates on the PR branch with the production-grade stack merged in: tsc --noEmit 0 errors, 1190/1190 tests green, lint clean.
  • One addition pushed (ba0bace): the members route's strict-gate comment now explains the who-is-acting asymmetry from its own side and points at the drift pins — previously the rationale only lived in the accept route and the docs.

Merging.

@teetangh teetangh merged commit 14f028d into feature/enterprise Jun 10, 2026
1 check passed
@teetangh teetangh deleted the fix/learner-accept-lazy-create branch June 10, 2026 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant