2026-07-17 — XP-141 master-data quick wins (PERF-7)
What shipped
Section titled “What shipped”packages/service/src/subjects/subject-update.ts— the two level-overrideCOUNTs (student + tutor price overrides, only on thehas_levels: true→falseguard path) now run via onePromise.all.SubjectHasLevelOverridesError+ total unchanged.packages/service/src/academic-years/activate-academic-year.ts— the max-1-ACTIVE guard read (existingActive) + the independentbeforefetch collapsed into onePromise.allbefore the UPDATE.packages/service/src/enrollment/student-subject-level-set.ts—subject+beforefetches hoisted into onePromise.all; the level fetch stays sequential/conditional aftersubject.has_levels(dependent — must not parallelize). XP-28 archived-level guard untouched.
Key decisions
Section titled “Key decisions”Promise.all(not raw scalar-subquery SQL) is the right rung: same round-trip win, fully type-safe, no fragile SQL. Only the timing of independent reads changed; evaluation order + thrown errors are byte-identical.- academic-year guard still evaluates FIRST (
if (existingActive) throwsits immediately after the await), so the max-1-ACTIVE invariant + error ordering are preserved perdomain-academic-year.md. Extrabeforeread only happens on the guard-fail path; success path is one round-trip fewer.
Gotchas / lessons
Section titled “Gotchas / lessons”Promise.allis safe inside a drizzledeps.db.transaction: the tx reserves one postgres-js connection and postgres-js pipelines concurrent queries on it — no “another query in progress” error. Common assumption is you must keep tx reads sequential; you don’t, unless one read depends on another’s result. Promoted toservice-conventions.mdQuery-shape section.
Reference changes
Section titled “Reference changes”.claude/rules/service-conventions.md — Query-shape section gained the “Promise.all is safe inside a tx (postgres-js pipelining)” note (XP-141).