Skip to Content

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:

FilterDescription
subSkillIdMust match the sub-skill being practiced or assessed
taskModepractice, quick_check, or progress_monitoring_probe; each has distinct UX rules
scaffoldTierLevel_1_Foundation, Level_2_Targeted, or Level_3_Independence (progress_monitoring_probe skips tier)
difficultyBandDifficulty band anchored to delta_prior
dialectMSA / LEV; items whose dialect tags are absent are treated as dialect-agnostic
Recently servedItems 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.

Serve outcomes

Every serve response is HTTP 200. When no item is served, served is null and reason carries one of the non-ok outcome codes. The caller is responsible for choosing the correct UX for each case.

OutcomeservedMeaningWhat to do
okitem payloadItem selected and passed all checksRender the item
no_eligible_itemnullNo item passed the filter combinationSurface the gap UX; the outcome writes a gap-queue row
blocked_by_visibility_rulenullAn item was selected but failed the student-visibility wrapper at serve timeTreat as a content-pipeline error; surface the same gap UX as no_eligible_item
failednullUnexpected internal failureRetry once; if it persists, escalate

no_eligible_item

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.

blocked_by_visibility_rule

The visibility wrapper runs a final check on the selected item’s content payload immediately before the serve is written. If the check finds an internal label that must not reach a student, the serve is aborted and logged as blocked_by_visibility_rule. The import gate is designed to prevent such items from reaching operational status, so this outcome indicates a content-pipeline gap that needs investigation.

From a caller perspective, blocked_by_visibility_rule should be treated the same as no_eligible_item: served is null, the UX should reflect the absence of an item, and the incident appears in the gap summary so the content team can investigate.

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

MethodPathPermissionNotes
POST/api/delivery/taskVIEW_OWN_CLASSES or RUN_BACKOFFICEServe one item; served:null is 200
GET/api/delivery/log/{taskInstanceId}RUN_BACKOFFICEFull serve audit for one instance
GET/api/admin/delivery/gap-summaryRUN_BACKOFFICEGap counts by (sub_skill × tier × mode)
POST/api/admin/delivery/dry-run-replayRUN_BACKOFFICEV-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.