Task Delivery
The task delivery service (WP-DTE-BE, modules/delivery) selects a stored, render-ready item from the item bank and serves it exactly as stored. The platform never composes, templates, or generates content at runtime.
Item-bank-only model
All items are authored and validated offline, then imported into the item bank through an import pipeline that runs Arabic agreement checks (gender, number, case), metadata validation, language-safety screening, and difficulty-prior (delta_prior) computation at import time. By the time an item enters the bank it is ready to be served without any runtime transformation.
When the delivery service runs, it does exactly three things: filter, serve, and log. No content is assembled, no language model is invoked, and no template is expanded.
Selection filters
Each serve request specifies a target context. The engine applies the following predicates in combination:
| Filter | Description |
|---|---|
subSkillId | Must match the sub-skill being practiced or assessed |
taskMode | practice, quick_check, or probe; each has distinct UX rules |
scaffoldTier | Level_1_Foundation, Level_2_Targeted, or Level_3_Independence (probes skip tier) |
difficultyBand | Difficulty band anchored to delta_prior |
dialect | MSA / LEV; items whose dialect tags are absent are treated as dialect-agnostic |
| Recently served | Items served to the same student in the recent-serve window are excluded |
When multiple items pass all filters the engine applies a fully deterministic tie-break: closest difficulty band first, then delta_prior ascending, then itemBankId lexicographic. The selection is byte-identical for identical inputs, which satisfies the V-6 determinism rule.
Scaffold tier resolution
The scaffold tier is per (student × skill × bundle × task type), never a global student-level attribute. For tiered task modes (practice or quick_check within an active bundle) the delivery service looks up the student’s student_scaffold_assignment row. If no assignment exists the serve returns no_eligible_item. A random fallback tier is never applied.
Probes (diagnostic quick_check outside a bundle) select tier-agnostically.
The no_eligible_item response
When no item passes all filters the response is still HTTP 200: the serve did not fail. The response body carries served: null plus a reason of no_eligible_item. The caller is responsible for surfacing the right UX.
Every no_eligible_item outcome writes a gap-queue row. The admin gap summary endpoint aggregates these rows by (sub_skill × tier × mode) so that offline content authoring teams know exactly which combinations need new items.
no_eligible_item is not an error. It is a gap signal. Never treat it as a 4xx or 5xx. Surface it to the content pipeline so the gap is filled offline.
Served-task-instance audit
Every successful serve writes a served_task_instance row with a UUID primary key. This UUID is the join token for printed QR worksheets (session-gen and paper-scan flows). UUIDs are non-enumerable and globally unique, making them safe to embed on physical paper, unlike auto-increment integers.
The audit row pins the following for permanent reproducibility:
- Item-bank snapshot version and selection-policy version
- Scaffold tier and rule version used
- The recently-served exclusion window
- Request context (sub-skill, mode, band, dialect)
Dry-run replay (V-6 verification)
POST /api/admin/delivery/dry-run-replay re-runs item selection against the same pinned versions. It writes nothing. The response reports whether each replayed request returns the byte-identical item. A false result is a determinism regression.
API reference
| Method | Path | Permission | Notes |
|---|---|---|---|
POST | /api/delivery/task | VIEW_OWN_CLASSES or RUN_BACKOFFICE | Serve one item; served:null is 200 |
GET | /api/delivery/log/{taskInstanceId} | RUN_BACKOFFICE | Full serve audit for one instance |
GET | /api/admin/delivery/gap-summary | RUN_BACKOFFICE | Gap counts by (sub_skill × tier × mode) |
POST | /api/admin/delivery/dry-run-replay | RUN_BACKOFFICE | V-6 determinism assertion; read-only |
There is no student-facing delivery endpoint. All delivery routes require a teacher or admin permission. Students never make direct calls to this service (B.17).
What delivery does not do
- It does not generate or compose content at runtime.
- It does not invoke a language model at any point.
- It does not fall back to a substitute item when the requested tier has no match.
- It does not validate Arabic agreement at serve time (that check runs at import).
- It does not expose a student-facing endpoint.
Related
- Skills Taxonomy: sub-skill IDs used as selection keys
- Student Profiles: scaffold tier source
- Intervention Bundles: bundle context that governs tier selection
- Language Safety: import-time Arabic agreement and safety gate