Repository navigation
Conversation
…ficate template The validate step rewrites the deploy payload through the Course model, and pydantic drops undeclared fields, so certificateSettings in course.json never reached the API. An author could not enable certificates or choose a design at deploy time; only the qBook admin UI could. Course gains an optional certificateSettings (enabled, criteria, templateId) mirroring qbraid-api's validator: templateId is one of qbraid, quera, qct; enabled is a strict boolean. Course.to_payload omits the object when absent (and criteria when absent inside it), because the API refuses an explicit null there, unlike durationWeeks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
CertificateCriteria.value currently allows unintended coercions (e.g., booleans / numeric strings) that can let invalid course.json pass validation and fail later at deploy time.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
What changed in this PR
This PR extends the Course validation/serialization pipeline so course.json can declare certificateSettings (including certificate enablement criteria and an optional certificate template), and ensures the deploy payload preserves these fields instead of dropping them during the Pydantic model round-trip.
Changes:
- Added
CertificateSettings/CertificateCriteriamodels and aCourse.certificateSettingsfield to validate certificate configuration early. - Introduced
Course.to_payload()and switched the validate step to writecourse_data.jsonusing it, omittingcertificateSettings(and nestedcriteria) when absent to avoid sending explicitnull. - Added unit tests covering forwarding behavior, omission behavior, and several invalid-shape/invalid-enum validation failures.
| File | Description |
|---|---|
src/scripts/validate_course.py |
Adds certificate models and to_payload() to correctly forward/omit certificateSettings in the deploy payload. |
test/unit/test_validate_course.py |
Adds tests ensuring certificateSettings is forwarded and invalid inputs fail validation. |
CHANGELOG.md |
Documents the new forwarding behavior for certificateSettings. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| class CertificateCriteria(BaseModel): | ||
| """When a learner earns the certificate: a completion percentage or a | ||
| points total, both of which the qBraid API accepts as `value >= 0`.""" | ||
|
|
||
| type: Literal["completion", "points"] | ||
| value: float = Field(..., ge=0) | ||
|
|
|
Exercised live through the Action from a qBraid-tutorial workflow (
|
|
Exercised live: the Action deployed a new course into a preview of qbraid-api #1093/#1136/#1156, first in create mode and then in update mode. Checked live, working
Findings
|
An author had no way to learn the valid templateId values short of a failed validation. Lists the fields, the three designs, and what an omitted field does on redeploy, including that an omitted criteria resets to 100%. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
quera and qct are reserved for their partner organizations by the qBraid API, and a deploy naming one the course's org cannot use fails with 403 CERTIFICATE_TEMPLATE_NOT_ALLOWED. Documents that, and that a course keeps a design it already has. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CertificateSettings and CertificateCriteria used pydantic's default extra="ignore", so a typo such as templateID passed validation and the course deployed with the default design. Both models now forbid extra keys, and the error location names the unknown key. criteria.value is strict and finite: "80", true, inf and NaN are refused, not coerced. The README and changelog said quera was open to any course deployed to quera.com. The qBraid API grants nothing by deploy domain: a partner design needs a grant on the organization, set by qBraid staff, and the API otherwise answers 403 CERTIFICATE_TEMPLATE_NOT_ALLOWED. A new course with no templateId gets quera only on quera.com with that grant. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The spell checker flagged the deliberate misspelling. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@avanbussum Ready for review. Changes from the internal pass: certificateSettings and criteria now reject unknown keys (a typo like templateID used to be dropped silently), and criteria.value is strict (9d038c1). The README's quera rules are corrected: a partner design needs an org grant. |

Summary of changes
Stacked on #33 (base is
feat/course-duration-weeks, notmain); merge #33 first.The same round-trip that dropped
durationWeeksalso dropscertificateSettings: the validate step rewrites the payload through theCoursemodel, and pydantic ignores fields the model does not declare. So an author could not enable certificates or choose a certificate design fromcourse.json; only the qBook admin UI could, after the deploy.Coursegains an optionalcertificateSettingswithenabled(strict bool),criteria({type: completion|points, value >= 0}) andtemplateId(qbraid,queraorqct), mirroring the qBraid API's validator so a typo fails at validation with a field-level message instead of a 400 at deploy time. Both models forbid unknown keys, sotemplateIDfails with an error naming the key instead of being dropped.criteria.valueis strict and finite:"80",true,infandNaNare refused, not coerced.Course.to_payload()replaces the baremodel_dumpat the write site. It omitscertificateSettingswhen absent (andcriteriawhen absent inside it), because the API's schema isoptional(object)and refuses an explicitnull.durationWeekskeeps its documentednull.QCT,thailand, missingenabled,"yes",grade, negative value, string value, boolean value,inf,NaNall fail validation; an unknown key (templateID,criteria.valu) fails with anextra_forbiddenerror whose location names that key.Example
course.jsonaddition:On the API side,
POST /learn/articles/course/ingestandPUT /learn/articles/course/:customIdalready accept this shape; qBraid/qbraid-api#1136 makes the create path persisttemplateIdand preserves a stored template when a redeploy omits it.Validation run:
pytest test(198 passed: 188 unit, 10 e2e),make format-checkclean.Partner designs (
quera,qct) need a grant on the course's organization, set by qBraid staff; the deploy domain grants nothing. Without the grant the API answers403 CERTIFICATE_TEMPLATE_NOT_ALLOWEDand the action fails showing that response. A new course with notemplateIdgetsqueraonly on aquera.comcourse whose organization holds thequeragrant, otherwiseqbraid. The README says this.Coordinated PRs
templateId; redeploys without one keep the stored value)qctdesign)Deploy a course naming
qctonly once #1136 and #40 are live on the target environment; before that the API's enum rejects it with a 400 naming the allowed values.🤖 Generated with Claude Code