Skip to content

fix(remark): map indicator_ordinal → ordinal — no principal feedback has ever reached a teacher (prod cherry-pick) - #343

Open
hatafatif wants to merge 2 commits into
mainfrom
bd-43488-prod-1787298016
Open

fix(remark): map indicator_ordinal → ordinal — no principal feedback has ever reached a teacher (prod cherry-pick)#343
hatafatif wants to merge 2 commits into
mainfrom
bd-43488-prod-1787298016

Conversation

@hatafatif

Copy link
Copy Markdown
Collaborator

Production cherry-pick of a118b57 (from develop, PR #342). Cherry-picked rather than merged because develop is 301 commits ahead of main with unrelated work.

Reported by Hasnat, 2026-08-17:

There is also a breakage in the feedback flow: teachers are currently not receiving the feedback submitted by the principal.

Confirmed on production, and worse than reported — the principal's confirmation is lost too.

Root cause

remark-delivery.deps.js selected indicator_ordinal and returned rows untouched. remark-rubric.js:243 requires ordinal and throws on anything else. That computeS call sits at remark-delivery.service.js:100above both try/catch blocks — so the throw escaped submitRemark, taking the narrative, the teacher's note and sendToPrincipal with it.

It looked healthy because scores persist before the failure and the principal is shown SUCCESS before the async delivery runs.

remark-narrative-retry.worker.js:61 has always done this mapping. The same query, mapped in one file and not the other, is the whole bug — and that worker is scheduled nowhere, so nothing drained the backlog either.

Verified against main's code

Driving the real submitRemark on this branch:

row shape outcome
raw (indicator_ordinal) throws malformed score row (missing ordinal){teacher:0, principal:0, generated:0}
mapped (ordinal) {teacher:1, principal:1, generated:1}, deliveryPending: false

Remark suite on this branch: 182/182. Cherry-pick applied with no conflicts — the remark files are byte-identical between main and develop.

Blast radius

One line in loadScores, plus a new test file. No schema change, no Flow asset change, no shared-service edit. WhatsAppService.sendMessage is untouched.

Known follow-ups (NOT in this PR)

  • 24h window: sends are free-form type: 'text' with no template fallback, so a teacher who has not messaged the bot in 24h will hit Meta 131047. A teacher being evaluated is exactly the person unlikely to have messaged recently — this fix may relocate the failure rather than remove it. Check Axiom responseData.error.code on the first real submissions after deploy.
  • Retry worker unscheduled: once wired it will sweep the accumulated backlog and fire notes about weeks-old evaluations. Drain deliberately.

Refs: bd-43488

…ches a teacher

loadScores selected `indicator_ordinal` and returned the rows untouched. computeS
requires `ordinal` and throws on anything else, and that call sits ABOVE both of
submitRemark's try/catch blocks — so the throw escaped the whole function and took
the narrative, the teacher's coaching note AND the principal's confirmation with
it. Scores still persisted (written before the failure) and the principal still
saw the SUCCESS screen (returned before the async work), so the flow looked
healthy while no teacher had ever received a note.

remark-narrative-retry.worker.js has always done this mapping. The same query,
mapped in one file and not the other, is the whole bug — and the worker is
scheduled nowhere, so nothing drained the backlog either.

Verified end to end by driving the real submitRemark: with the raw row shape it
throws with teacher/principal/generated/marked all at 0; with the mapped shape
both sends fire and deliveryPending is false.

Tests: new remark-delivery-deps.test.js pins the boundary the two halves
disagreed about — nothing exercised makeDeliveryDeps before, which is why 177
tests passed while production sent nothing. Full remark suite 182/182.

Reported by Hasnat, 2026-08-17.

Closes: bd-43488
`main` is failing its own logger-level ratchet — 390 live ❌-without-level
callsites against a 389-entry allowlist — so every PR into main is blocked. The
drift came in with 180370e, which added this callsite without re-baselining.

Fixed by adding the severity argument rather than grandfathering it, which is
what gen-logger-allowlist.js asks for: an unbound capture is a real failure and
belongs at error, not info, where it maps to console.log and never surfaces as
an error in Axiom. The allowlist is left untouched and now reports in sync,
0 new / 0 stale.

Ratchet 3/3. No new observe failures against origin/main (8 pre-existing suites
fail identically before and after).
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.

1 participant