Skip to content

feat: add tenant-safe enrollment vertical - #5

Open
seonghobae wants to merge 6 commits into
feat/external-learner-kernelfrom
feat/enrollment-vertical
Open

feat: add tenant-safe enrollment vertical#5
seonghobae wants to merge 6 commits into
feat/external-learner-kernelfrom
feat/enrollment-vertical

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add tenant-scoped course offerings and external entitlement projections
  • add enrollment and learning-registration endpoints with active membership/entitlement checks
  • extend PostgreSQL composite foreign keys, uniqueness, and RLS policies
  • extend the real PostgreSQL/API CI smoke path and preserve the PR4 least-privilege application role
  • reconcile the current PR4 base and order rollback drops by foreign-key dependency

Evidence

  • exact current head b92fc86438b881c2d83cb2d98f8d21337811623f
  • base is current PR4 head 4ce89f96310d41eaa3b12cf4afa12c36a72462dd
  • actionlint .github/workflows/quality.yml
  • cargo +1.97.1 fmt --all -- --check
  • cargo +1.97.1 test --workspace --all-targets --locked
  • cargo +1.97.1 clippy --workspace --all-targets --locked -- -D warnings
  • RUSTDOCFLAGS='-D warnings' cargo +1.97.1 doc --workspace --no-deps --locked
  • local PostgreSQL with a NOSUPERUSER NOBYPASSRLS role: 12 tenant policies, learner 201, duplicate learner 409, offering/entitlement/enrollment/registration 201, rollback tables 0, and re-applied policies 12
  • git diff --check

Scope boundary

This is the registration/enrollment slice of issue #3. Progress projection, assessment, completion persistence, credential issuance, released external contracts, browser E2E, and production readiness remain follow-up work.

Review request

Please review the exact current head b92fc86438b881c2d83cb2d98f8d21337811623f against 4ce89f96310d41eaa3b12cf4afa12c36a72462dd. Do not treat local or green CI checks as semantic approval.

@coderabbitai

coderabbitai Bot commented Aug 20, 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: CHILL

Plan: Pro Plus

Run ID: 02481223-e030-41b2-aa70-3ffdef66014f

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@seonghobae

Copy link
Copy Markdown
Contributor Author

Please perform an independent review of the exact current head 5742e114279bc29917e25388d64f28dee7320c25 against base feat/external-learner-kernel. Re-check tenant isolation, RLS/composite-FK boundaries, effective entitlement validity, duplicate/idempotency behavior, migration execution, and CI smoke coverage. Do not treat green checks as semantic approval.

@seonghobae
seonghobae force-pushed the feat/external-learner-kernel branch from 7c7a846 to b832ed6 Compare August 20, 2026 11:07
@seonghobae
seonghobae force-pushed the feat/enrollment-vertical branch from 5742e11 to a3e2269 Compare August 20, 2026 11:08
@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent Please review the current exact head a3e22695e28b7a3a4eba42c4510c075d304aeefb against base feat/external-learner-kernel; the previous 5742e114 request is stale after rebase. Re-check enrollment vertical behavior, RLS/composite-FK boundaries, migration execution, and CI smoke coverage. Return an independent semantic verdict only; do not self-approve.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent Please review the current exact head 820402ced1ed48b68e3d37001af07476908e48c9 against base feat/external-learner-kernel; the previous a3e2269 request is stale after binding completion decisions to learning registrations. Re-check the new non-null tenant-scoped registration FK, migration execution, and all enrollment smoke behavior. Return an independent semantic verdict only; do not self-approve.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Current-head review request for ae4c9c8. Re-verify the exact-head diff, required Checks, tenant/security boundaries, and any valid review findings after the shared Rust component-install fix. COMMENT only; no approval or protected-merge bypass is requested.

…to HEAD

# Conflicts:
#	.github/workflows/quality.yml
#	CHANGELOG.md
#	README.md
#	docs/ARCHITECTURE.md
#	docs/DATA_MODEL.md
#	docs/product-technical-gap-baseline.md

Copy link
Copy Markdown
Contributor Author

@OpenCode review only the exact current head b92fc86 against base 4ce89f9. Re-check enrollment tenant isolation, entitlement/membership validity, registration cardinality, rollback dependency order, and the merged PR4 workflow. Leave an independent review or approval for this exact head only. Do not update code, merge, self-approve, bypass protections, or treat queued Checks as proof.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

Open in Devin Review

CONSTRAINT learning_registration_enrollment_fk
FOREIGN KEY (tenant_id, enrollment_record_id)
REFERENCES enrollment_record (tenant_id, enrollment_record_id),
CONSTRAINT learning_registration_enrollment_unique UNIQUE (tenant_id, enrollment_record_id),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Learners can never re-register for an enrollment they already registered once

A learning registration is limited to one per enrollment by a uniqueness rule (learning_registration_enrollment_unique at migrations/0001_learning_kernel.sql:199), which contradicts the platform's own documented model that allows an enrollment to have many registrations, so a learner whose registration is closed can never register again.
Impact: Any attempt to register a second time against the same enrollment (for example after a prior registration is closed) is permanently rejected, blocking the documented retry/re-registration journey.

Constraint vs documented cardinality mismatch

migrations/0001_learning_kernel.sql:199 declares CONSTRAINT learning_registration_enrollment_unique UNIQUE (tenant_id, enrollment_record_id), forcing a 1:1 relationship between enrollment_record and learning_registration. However docs/DATA_MODEL.md:49 states the cardinality baseline as enrollment_record 1 ---- * learning_registration (one-to-many). The API handler create_registration (crates/lms_kernel/src/bin/lms_api.rs:423-468) relies on this uniqueness so that a second registration attempt raises a 23505 and is mapped to 409 (crates/lms_kernel/src/bin/lms_api.rs:43-52). Given the registration_status lifecycle includes closed (migrations/0001_learning_kernel.sql:190-191) and the acceptance slice requires retry/out-of-order behavior to be tested (docs/product-technical-gap-baseline.md), the 1:1 constraint is likely stricter than intended. Either the cardinality baseline should be corrected to 1:1, or the constraint should be relaxed to permit the documented one-to-many relationship.

Prompt for agents
The new learning_registration table (migrations/0001_learning_kernel.sql:184-201) adds CONSTRAINT learning_registration_enrollment_unique UNIQUE (tenant_id, enrollment_record_id), which enforces exactly one learning_registration per enrollment_record. This contradicts the documented cardinality baseline in docs/DATA_MODEL.md:49 which states enrollment_record 1 ---- * learning_registration (one-to-many), and conflicts with the acceptance requirement that retry / re-registration journeys be exercised. Decide the intended cardinality: if one registration per enrollment is truly intended, update docs/DATA_MODEL.md line 49 to reflect a 1:1 relationship; if many registrations per enrollment are intended, remove or loosen the learning_registration_enrollment_unique constraint (and reconsider the create_registration handler in crates/lms_kernel/src/bin/lms_api.rs which currently depends on the uniqueness to return 409 on duplicates).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

--header 'content-type: application/json' --data "$payload" \
"http://127.0.0.1:8080/v1/tenants/$tenant_id/learners")" = '409'
learner_id=$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1]))["learner_id"])' "$RUNNER_TEMP/lms_api_response.json")
offering_id=$(curl --fail --silent --show-error --request POST \
--data '{"offering_name":"Partner Academy Basics","content_release_reference":"content-release/v1/partner-basics"}' \
"http://127.0.0.1:8080/v1/tenants/$tenant_id/offerings" \
| python3 -c 'import json, sys; print(json.load(sys.stdin)["course_offering_id"])')
entitlement_id=$(curl --fail --silent --show-error --request POST \
--data '{"source_authority":"billing-control-plane","external_entitlement_reference":"entitlement-ci-1","source_digest":"sha256:ci","source_version":"v1"}' \
"http://127.0.0.1:8080/v1/tenants/$tenant_id/learners/$learner_id/entitlements" \
| python3 -c 'import json, sys; print(json.load(sys.stdin)["access_entitlement_id"])')
enrollment_id=$(curl --fail --silent --show-error --request POST \
--data "{\"course_offering_id\":\"$offering_id\",\"access_entitlement_id\":\"$entitlement_id\"}" \
"http://127.0.0.1:8080/v1/tenants/$tenant_id/learners/$learner_id/enrollments" \
| python3 -c 'import json, sys; print(json.load(sys.stdin)["enrollment_record_id"])')
curl --fail --silent --show-error --request POST \
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.

2 participants