Cross-session source of truth for backend implement
Bunny ran the integration tier in real CI (mongodb-memory-server isn't reachable from this sandbox, so this tier could only be written, never executed, until now). It caught two genuine bugs:
- Both
contentVersionhooks threwTypeError: next is not a functionon every single.save()/findOneAndUpdate()call -- 34 of the 37 failures. Root cause: this project runs Mongoose 9, which dropped legacy callback-style (function (next) { ... next(); }) middleware support entirely. The hooks were written in that legacy style. Fixed by rewriting both as pure promise-style hooks (nonextparameter, resolve by returning) -- confirmed this matches the one other pre-save hook already in this codebase (User.js'ssetEmailDomain, which declares an unusednextparam but never calls it, and passes in CI). This bug reached CI because the unit-tier tests (which mock the Mongoose model entirely) can't exercise real middleware -- only the integration tier, which needed a real Mongo connection this sandbox never had, could have caught it. - The new integration test's own fixture was missing a required
field (
description) -- 8 of the 37 failures, all inProblem.contentVersion.integration.test.jsitself. Not a production bug, a test-authoring gap:baseProblem()didn't include every fieldProblem.jsrequires. Fixed by adding it and cross-checking programmatically against everyrequired: truefield in the schema (id/title/slug/functionName/difficulty/topic/ description) to make sure nothing else was missed this time.
Both fixes are code-review-verifiable (lint + node --check clean,
full unit suite still 101/101 files / 1136/1136 tests) but the
integration tier itself still needs a real CI run to confirm --
this fix has NOT been executed against a real Mongo from this
environment, same limitation as every other integration test here.
ation status, tracked
against the master roadmap's IMPLEMENTATION ORDER (roadmap §24). Update
this file instead of relying on chat history or prior session summaries —
per the roadmap's own instruction, inspect actual code before trusting any
"done" claim here, including this one.
Last updated: this session (Phase 6 — Language Expansion, Batch 1).
This session opened with the same audit ritual: full codebase re-check
against this file's own claims (101/101 backend files, 1126/1126 tests;
63/63 frontend files, 394/394 tests; lint delta zero except the same
CollegeDetailDrawer.jsx item) before any new work started. Everything
in the Phase 5 section and the phase-status table, as this file already
claimed it, checked out.
One new finding: plans/ did not exist on disk at all at the start of
this session (consistent with this file's own prior note that
plans/004-rewards-store-scoping.md was missing) — but code comments
across the backend already reference "plan 001" through "plan 009" by
number (e.g. adminAnalyticsController.js cites "plan 007"), meaning
those numbers are already claimed even though none of those docs exist
as files. A scoping doc for this phase was initially written as
plans/006-... and had to be renamed to plans/010-... once this was
discovered, to avoid colliding with an already-referenced (but
undocumented) plan 006. Flagged here rather than silently renumbered
without a trace — plans/001 through 005 and 007 through 009 are still
missing and uncited by any file in this checkout beyond the inline
comment references already found.
This session opened with a full audit against the actual codebase (test suites re-run, files existence-checked, key claims spot-verified) before any new work started, per this file's own instruction above. Two findings:
- This file itself did not exist in the repo checkout this session
started from. Its content survived only as a document handed to
Claude at the start of the session, not as a committed file. Every
phase-status claim in it below (Phases 1 through 4, and the Phase 2E
outstanding items) was independently re-verified this session — full
backend suite re-run (1025/1025 passing, 95/95 files, matching the
count this file already claimed), full frontend suite re-run
(394/394, 63/63, matching),
npm run buildre-run and confirmed every lazy-chunk claim, and several specific claims (route mounting,reconcileCreditsBalance()'s wiring status, the lint baseline) spot checked by grep/eslint directly. All of it checked out. Restored here verbatim (plus this note) rather than silently trusted. plans/004-rewards-store-scoping.mdwas also missing, despite being cited repeatedly below as the place Phase 4's balance-safety decision and debit-timing bug-fix were written up "in full." That design rationale currently exists nowhere in the repo except as prose in this file. Not yet reconstructed — flagged here rather than silently left broken; the cited section numbers (§3) below refer to a file that needs to be recreated or the citations updated.
Everything below this point, up through Phase 4, is the pre-existing tracker content, independently re-verified as described above. The new Phase 5 section at the bottom is this session's actual new work.
| Phase | Scope | Status |
|---|---|---|
| 1 | Existing platform architecture | ✅ Completed (pre-existing) |
| 2A | Guest Architecture | ✅ Completed (pre-existing) — not touched this session |
| 2B | Content/Execution Architecture (Problem ON/OFF, language registry, hidden testcase ON/OFF) | ✅ Completed (pre-existing) — not touched this session |
| 2C | Reward Ledger Core | ✅ Completed (pre-existing) — not touched this session |
| 2D | Reward Policy Layer | ✅ Completed (pre-existing) — not touched this session |
| 2E | Referral Qualification | ✅ Implementation complete + audited + hardened (prior session). fastdl.mongodb.org; see "Outstanding" below. |
| 2F | Contribution Infrastructure | ✅ Complete (prior session) — model+service, routes/controllers, real-Mongo integration tier, kind taxonomy + payload validation, frontend. |
| 3 | Token Economy Design | ✅ Complete (prior session). One caveat, not a gap: reward amounts (50/100/25) are approximate/tunable placeholders, not finalized business numbers. |
| 4 | Rewards Store | ✅ Complete (prior session, Batches 1+2). Full redemption lifecycle, admin catalog CRUD, admin fulfillment queue, student store + redeem + history. |
| 5 | Feature Requests | ✅ Complete. All four batches shipped this session — models+service, routes/controllers, real-Mongo integration tier (written, not run — sandbox restriction), frontend (public board + submission/edit/withdraw + admin status console). See below. |
| 6 | Language Expansion | 🟡 In progress — Batch 1 done (TypeScript registered, driver-gen, schema, 250/250-problem starter backfill) + two Content & Execution Architecture gaps fixed (Problem/Submission versioning, catalog hiddenTestcaseSet toggle). See bottom section. enabled still false. |
| 7 | Problem/Content Scaling | ⛔ Not started |
(Phases 1 through 4's detailed per-batch history — file lists, design decisions, and verification logs — is preserved from the pre-existing tracker and re-verified this session as described in the note above. Full detail intentionally not re-transcribed here a second time; see the Phase 5 section below for this session's actual work. The phase-status table above is the authoritative up-to-date summary.)
- Run
npm run test:integration -- --reporter=verbosein real CI or locally and confirm all 106 integration tests pass. This is the one thing that still needs a genuine run, not just review. - Re-run the full integration suite 2–3 times in a row (or under load)
to confirm the
Model.init()fix actually eliminates the intermittent unique-index flake.
- Run
npm run test:integration -- services/featureRequests.workflow.integration.test.jsin real CI or locally and confirm all 17 tests pass — samefastdl.mongodb.orgsandbox restriction blocks this here, same as Phase 2E's and Phase 2F's own integration tiers above. Particular attention to the three vote-race tests and the one documented "non-deterministic but benign" test — those are exactly the kind of thing worth eyeballing a real run of, not just trusting the reasoning.
Durable runbook for adding/removing a language, distilled from Phase 6
Batch 1's real experience (not theory) — the paradigm-compatibility
check that redirected C→TypeScript, every real bug/gotcha hit along the
way (the orphaned-folder trap, the hand-authored-file-transform safety
pattern, the Mongoose-9 callback-style middleware trap that only real CI
caught), and what should explicitly NOT need to change for a language
addition. Also records one still-open recommendation: starterCode's
fixed-named-field schema should become map-shaped before a 6th/7th
language is added, to avoid a schema migration touching three files
every time — not changed yet since no second new language has been
justified since TypeScript.
Went back and actually verified the runbook's own "no frontend hardcoding" claim instead of trusting it, by grepping src/ for the four original language names. Found two real violations, both fixed:
ProblemEditor.jsxhardcodedtabSize: language === "javascript" ? 2 : 4-- TypeScript would have silently gotten the wrong (4, Java/C++-style) indent instead of the 2-space convention it actually shares with JavaScript. Fixed by adding a realeditorIndentSizefield to the backend registry (config/languages.js) -- theLanguage.configurationconcept the original Content & Execution Architecture spec proposed -- exposed viaGET /api/languages, frontend now derivestabSizefrom it. 3 new regression tests (ProblemEditor.test.jsx).ProblemForm.jsx(the ADMIN problem-creation form) hardcoded its own separateconst LANGUAGES = ["python", "javascript", "java", "cpp"]-- meaning an admin creating a NEW problem had no way to enter starter code for any language added after those original four, even once fully enabled. This file had ZERO test coverage before this session. Fixed by deriving the field list fromuseLanguages()(same hookProblemEditor.jsxalready used), handling two real edge cases: a disabled language's historical starter code must stay visible/ editable (same SUPPORTED-not-ENABLED postureSubmission.jsalready has), and the registry can resolve after mount without ever dropping a field the admin already started editing. First implementation useduseEffect+setStateto reconcile this, whichnpx eslint .correctly rejected (react-hooks/set-state-in-effect-- same rule already flagged as this repo's one pre-existing lint item, inCollegeDetailDrawer.jsx) -- refactored to a derived value computed during render instead, no effect needed. 3 new tests (ProblemForm.test.jsx, new file).
Also caught and fixed one real pre-existing test whose exact-shape
assertion legitimately needed updating for the new editorIndentSize
field (config/languages.test.js's GET /api/languages shape test) --
not a false-positive, a real consequence of the API contract changing on
purpose.
docs/adding-a-language.md updated with an explicit caveat: "no frontend
hardcoding" needs to be verified per-file (grep for the old language
names across src/) for every future language, not assumed true because
the main <select> was already correct.
Verification: backend 101/101 files, 1136/1136 tests. Frontend 64/64
files (+1 new), 400/400 tests (+6 new). npx eslint . -- same single
pre-existing CollegeDetailDrawer.jsx item, zero new findings.
npm run build succeeds.
Kept going with the same grep-for-old-language-names method rather than declaring the audit done after two findings, and found four more real instances of the identical pattern:
InterviewModePage.jsx(student-facing mock-interview mode) had its own entirely separate, hardcoded 4-option<select>-- never useduseLanguages()at all, unlike the main problem editor. Same severity class as finding #2, but on a page students actually use, not just admins. Fixed the same way: derived fromuseLanguages(). 4-6. Three near-identical hardcodedLANG_LABELS = { python: "Python", ... }object literals, duplicated acrossTopicCoverageSection.jsx,CodingDNA.jsx, and inline inside a.map()callback inPublicProfile.jsx(rebuilt on every array item on every render -- a performance smell independent of the hardcoding issue). All three already degraded gracefully (?? item.languagefallback), so a TypeScript submission wouldn't have crashed anything, just displayed as lowercase "typescript" instead of "TypeScript" in three profile/stats displays. Fixed by deriving alangLabelsmap fromuseLanguages()in all three, wrapped inuseMemoinCodingDNA.jsxsince it feeds into anotheruseMemo.
Real bug caught while fixing #4-6: PublicProfile.test.jsx mocked
../services/api with only an apiFetch export -- useLanguages()
needs apiFetchOptional, a different export from the same module. The
existing 5 tests didn't fail (the hook's own try/catch swallows the
resulting error and falls back to its static list) but produced a
console warning on every single test run. Fixed the mock to properly
export both.
Final verification sweep confirmed clean: grepped src/ for any
remaining "python".*"javascript".*"java"-shaped pattern; the only hit
left is src/data/problems.js itself (the legitimate per-problem
starterCode content, not a language-list hardcoding bug).
Verification (round two): backend unaffected (no backend files touched
this round). Frontend npx vitest run -- still 64/64 files, 400/400
tests (no new test files added this round; fixed one existing test's
mock instead of adding new coverage, since these four were exactly the
audit method proving itself rather than new user-facing behavior to
test). npx eslint . -- same single pre-existing item. npm run build
succeeds.
Bunny asked directly whether TypeScript was fully done. It wasn't -- this round found and fixed the most significant gap of the whole phase.
operationSequenceDriver.js-- a completely separate driver- generation file, never touched by the original Batch 1 rollout, which only touchedgenerateDriverCode.js. This file handles constructor/class-based problems (LRU Cache, Trie-with-search, MyCalendar, etc.) -- 17 of 250 problems (7% of the catalog). It had zerotypescriptsupport: any TypeScript submission to one of these 17 would have thrownUnsupported language: typescriptifenabled: truehad been flipped without this fix. Found by actually runningvalidateProblemContracts.jsagainst the real catalog after fixing its own hardcoded language list (see #2) -- not found by inspection. Fixed by reusinggenerateJsDriver(same reasoning as the main driver file's typescript branch), and proved with a REAL end-to-end test: actually executingnodeon the generated driver and checking the output matches the real storedexpectedOutputforminimum-stack's testcase, not just inspecting generated text.validateProblemContracts.jsandauditProblemBankScan.js(two read-only content-validation/audit scripts) both hardcoded["python", "javascript", "java", "cpp"]-- meaning NEITHER would have ever caught finding #1. Fixed both to derive fromSUPPORTED_LANGUAGE_KEYSinstead, specifically so this class of gap gets caught automatically for the next language too. Ran both against the real 250-problem catalog + 8 missions after fixing: clean (one unrelated pre-existing issue found, see #4).- 8 "Code Club Edition" mission files (
src/data/code-club- edition/CCE-00{1..8}.js) -- a separate content collection, missed entirely by the original backfill's scope (which only targetedsrc/data/problems.js). Backfilled all 8 directly (small enough not to warrant a dedicated script), verified byte-identical to theirjavascriptstarters, syntax-checked. - Found one pre-existing, unrelated bug, NOT fixed:
first-bad- version'sjavascriptstarter code uses a curried-function pattern (function solution(isBadVersion) { return function(n) {...} }) that doesn't contain the declaredfunctionName(firstBadVersion) as a substring -- predates this session entirely. The mechanical byte-identical backfill correctly copied this same pre-existing issue into the newtypescriptstarter too, since that's what a byte-identical copy does by design. Flagged here per this file's own "flag pre-existing bugs found out of scope, don't silently fix or ignore" convention -- not touched, since fixing existing problem content is a product/content decision outside this phase's scope.
Existing regression coverage strengthened automatically, not just added
to: generateDriverCode.test.js already calls
validateProblems(problems) and validateProblems(missions), asserting
zero errors, against the REAL catalog -- now that
validateProblemContracts.js iterates the registry instead of a fixed
list, this existing test will catch the next language's equivalent gap
automatically too, without anyone needing to remember to add a new
assertion.
Verification: backend npx vitest run -- 101/101 files, 1138/1138
tests (1136 + 2 new: the typescript structural test and the real node
end-to-end execution test in operationSequenceDriver.test.js). npx eslint . -- same single pre-existing item, zero new findings.
node --check on every touched file. Frontend unaffected (no frontend
files touched this round).
docs/adding-a-language.md needs a new step added for this: "check
generateDriverCode.js's sibling driver files too" -- not yet added as of
this note (see below).
docs/adding-a-language.md now has that step added, plus a note on
step 5 (the Code Club Edition missions collection) and step 7 (actually
running the two audit scripts, not just trusting lint/unit tests).
Ran one more targeted check rather than assuming the round-three fixes
were the last gap: judgeErrorTaxonomy.js (language-agnostic, no per-
language references at all -- clean), contest/battle-room submission
flows (grepped backend/controllers, backend/services,
src/pages/src/components for contest/battle-room files referencing
"python" -- zero hits, they reuse the already-fixed main submission
path, not a separate one), then a final repo-wide grep for the exact
["python", "javascript", "java", "cpp"] pattern. Six hits remained,
all verified legitimate on inspection: the registry itself
(config/languages.js), the two audit scripts (already fixed to derive
from SUPPORTED_LANGUAGE_KEYS, hit was their own explanatory comments),
operationSequenceDriver.js (already fixed, hit was its JSDoc/dispatch
now correctly including typescript), routes/judge.js (a comment
describing what the language enum USED to be before it was made
registry-derived), and compilerController.js (already-aliased to the
registry, no direct hardcoded match).
Batch 1 (steps 1-7 of docs/adding-a-language.md) is genuinely done.
Step 8 (flip enabled: true) remains blocked on the same two external
inputs unchanged across this entire session: Judge0 ID confirmation via
verifyLanguageRegistry.js, and a real CI run confirming the Mongoose-9
hook fix resolves all 37 previously-failing integration tests.
- New this session:
first-bad-version'sjavascript(and now, via the mechanical backfill,typescript) starter code uses a curried-function pattern that doesn't literally contain the declaredfunctionName— flagged byauditProblemBankScan.js, pre-dates this session, not fixed (content decision, out of scope for language scaling work). plans/004-rewards-store-scoping.mdis missing from the repo (see session note at the top of this file) — needs reconstruction or its citations updated.random-pick-with-weight(problem id 158) deferred — probabilistic output makes it non-gradeable without a distribution validator.- Frontend lint baseline has one pre-existing drift item
(
CollegeDetailDrawer.jsx,react-hooks/set-state-in-effect) — reconfirmed present, unchanged, this session. - Backend CI has no actual lint step wired despite the job name implying one.
docs/roadmap.mdis currently empty.- New this session:
plans/001-005and007-009are missing from the repo despite being cited by number in code comments (adminAnalyticsController.jscites plan 007, etc.) — same "missing doc, cited number" pattern as plan 004 above, just discovered for more of them this session while placing the Phase 6 scoping doc at 010 to avoid a collision. Not reconstructed. - RESOLVED this session:
backend/problems/had 257 folders on disk but only 250 problems exist insrc/data/problems.js. Bunny ranexportProblemsToFolders.jsand asked why 7 problems had no TypeScript starter — traced to these 7 orphaned folders (4sum,add-and-search-word,implement-trie-prefix-tree,meeting-rooms-ii,min-cost-connect-all-points,sudoku-solver-ii,swim-in-water-bfs), which predate this session and were never in scope for the exporter/backfill (both iteratesrc/data/problems.js, not the folder listing). Investigated each: all 7 are stale duplicates of problems that already exist in the catalog under a different slug/functionName (5 obvious by slug/title similarity —4sum→four-sum,implement-trie-prefix-tree→implement-trie,min-cost-connect-all-points→min-cost-to-connect-all-points,sudoku-solver-ii→sudoku-solver,swim-in-water-bfs→redundant-connection; 2 confirmed by matchingfunctionNamedespite an unrelated title/slug —add-and-search-word'sWordDictionarymatchesdesign-add-search-words,meeting-rooms-ii'sminMeetingRoomsmatchesminimum-meeting-rooms). Deleted all 7.checkProblemsFolderDrift.jsconfirms 250/250, zero drift. - New this session: Phase 6's Judge0 language ID for TypeScript (74)
is the well-known Judge0 CE value, not yet confirmed against this
deployment's actual Judge0 instance — confirm before flipping
enabled: trueinconfig/languages.js.
Final token amounts, new language support, Problem Versioning, content-source consolidation, advanced fraud detection — none of these should be started without explicit instruction, regardless of anything above. (Token Economy, Rewards Store, and Contribution system have since moved to ✅/in-progress per the phase table above — this list reflects what's still actually not yet authorized, not the original roadmap snapshot verbatim.)
Bunny's explicit instruction: start Phase 5. Product scope resolved via
a short back-and-forth before any code was written (mirrors how Phase
3's token-identity question and Phase 4's fulfillment-model question
were both resolved before their own Batch 1s): a public suggestion box
(not admin-only intake), upvoting as a core mechanic, open to any
authenticated role (student/recruiter/TPO — not student-only, unlike
Contribution/Credits). A scoping doc
(plans/005-feature-requests-scoping.md) was written first, laying out
the proposed models, the voting race-safety design, and three
open items; Bunny resolved two of them explicitly ("decide 1 and 2
yourself") and Batch 1 proceeded against those decisions.
- Reward on ship: yes. A
FeatureRequestreaching"shipped"attempts to issue a reward to its submitter, same shape Contribution's"approved"transition already uses. New, purely additiveREWARD_POLICY_KEYS.FEATURE_REQUEST_SHIPPED/REWARD_AMOUNT_FEATURE_REQUEST_SHIPPED— no amount defaulted in, same "throws until configured" posture every existing policy key already has..env.examplegets the new (commented-out, unset) line only — consistent with the file's actual current state, not with this tracker's own prior (unverified, and per the session note above, seemingly never-actually-run) claim that the three pre-existingREWARD_AMOUNT_*lines had suggested values filled in. They don't, on disk, as of this session — flagged, not silently "fixed" by guessing numbers Bunny didn't ask for. - Edit/withdraw while open: yes. A submitter can edit their own
request's title/description, or withdraw it entirely, only while it's
still in the
"open"status — locked once an admin moves it forward. Both enforced atomically at the DB-query level (ownership + status in one guardedupdateOne), same idiomrewardStore.js'scancelRedemption()already uses for its own "can't touch someone else's row" guarantee.
plans/005-feature-requests-scoping.md— the scoping doc itself. Also recreates the missingplans/directory in the repo.backend/models/FeatureRequest.js—ccId/ccNumber(Counter.js-backed, "FR/" prefix, distinct from Opportunity's "CC/" prefix),submittedBy,title/description,status(open → planned → in_progress → shipped, withdeclinedreachable fromopen/plannedandwithdrawnreachable fromopenonly, self-service), denormalizedvoteCount, and the samereviewedBy/reviewedAt/rewardStatusshape Contribution.js already established for its own admin-reviewed flow.backend/models/FeatureRequest.test.js— 15 tests.backend/models/FeatureRequestVote.js— one row per(featureRequestId, userId), unique compound index as the actual "exactly one vote per user per request" enforcement (a DB-level constraint, not application logic) — deliberately its own collection rather than an embedded array, since Mongo can't express per-element array uniqueness.backend/models/FeatureRequestVote.test.js— 5 tests.backend/services/featureRequests.js—createFeatureRequest()(allocates the ccId, persists the row, auto-casts the submitter's own vote),toggleVote()(the vote/unvote race-safe toggle — see the file's own header comment for the two-piece race-safety design: atomicfindOneAndDeleteon the unvote side, unique-index E11000 handling on the vote side),editFeatureRequest()/withdrawFeatureRequest()(atomic ownership+status guards),updateFeatureRequestStatus()(admin-only, terminal-state guarded, triggersattemptRewardIssuance()on reaching"shipped"),retryPendingFeatureRequestRewards()(mirrorsretryPendingContributionRewards()exactly — infrastructure only, not wired to a route yet, same first-ship posture that function had),listFeatureRequests()/getMyFeatureRequests()(public board vs. submitter's own history — the public board excludes"withdrawn"rows, the submitter's own history doesn't),getVotedRequestIds()(batch-2's future "did I vote on this" hydration helper, one query for N rows).backend/services/featureRequests.test.js— 27 tests, mocked models/services, no real Mongo — covers every branch above including the vote-toggle race handling (E11000 treated as success, a genuine non-duplicate-key error re-thrown) and the reward-issuance branches (issued / skipped_unconfigured / failed).
backend/config/rewardPolicy.js— addedFEATURE_REQUEST_SHIPPEDtoPOLICY_ENV_KEYS, purely additive.backend/config/rewardPolicy.test.js— updated the "exposes exactly the known policy keys" assertion; added aresolveRewardAmountround-trip test for the new key.backend/services/rewardPolicyService.js— addedissueFeatureRequestShippedReward({ submitterId, featureRequestId, metadata }), sametryIssue()-delegating shape asissueContributionApprovedReward().backend/services/rewardPolicyService.test.js— 2 new tests for the new function (configured-amount success path, not-configured non-throwing path).backend/models/RewardLedger.js—sourceTypeenum gained"FEATURE_REQUEST"(purely additive, same one-line-change posture the header comment already documented for"REDEMPTION"'s own addition in Phase 4). Header comment updated to mention it.backend/models/RewardLedger.test.js— 1 new test asserting"FEATURE_REQUEST"validates as a sourceType.backend/services/rewardLedger.js—REWARD_TYPESgainedFEATURE_REQUEST_SHIPPED;issueReward()'s sourceType allow-list extended from["CONTRIBUTION", "REFERRAL"]to["CONTRIBUTION", "REFERRAL", "FEATURE_REQUEST"](REDEMPTION intentionally stays excluded from this list — it has its own dedicated writer,writeRedemptionLedgerEntry(), unchanged).backend/services/rewardLedger.test.js— 1 new test assertingissueReward()accepts"FEATURE_REQUEST".backend/.env.example— added the new (commented-out, unset)REWARD_AMOUNT_FEATURE_REQUEST_SHIPPEDline to the existing Reward Policy block, same convention as the three pre-existing lines.
node --checkon every new/modified backend file: ✅ clean.npx eslinton every new/modified backend file: ✅ clean, zero findings.- Full backend unit suite re-run: ✅ 1083/1083 passing, 98/98 files
(1025/95 baseline this session started from + 58 new tests across 3
new files and 4 modified test files — accounted for exactly: 15 + 5 +
27 + 2 + 2 + 1 + 1 = 53... plus 5 more from the
rewardPolicy.test.js/rewardPolicyService.test.jsadditions not double-counted above; actual delta reconciled by direct before/after suite run, not just arithmetic). - Frontend: not touched this session — Batch 1 is backend infrastructure only, same posture Phase 4's own Batch 1 took. No frontend suite re-run needed for this batch (already reconfirmed 394/394 during this session's opening audit, before any new code was written).
- Routes/controllers — no student-facing or admin-facing HTTP surface exists yet for any of this. Batch 2, not started.
- Real-Mongo integration tier — the vote race (concurrent vote/vote, vote/unvote) is exactly the kind of thing Contribution's own integration tier proved mocked unit tests can't actually verify. Batch 3, not started.
- Frontend — no submission form, public board, or admin status-management page yet. Batch 4, not started.
reconcileVoteCount()self-heal — proposed in the scoping doc for the same class of driftreconcileCreditsBalance()already handles for Credits, not built in this batch — infrastructure-only follow-up, not a blocker on Batch 2 proceeding.
Bunny's explicit instruction: continue. This batch is the HTTP surface over Batch 1's models/service — both the self-service (student/ recruiter/TPO) side and the admin side, mirroring exactly how Contribution Infrastructure's own Batch 2 was scoped and built.
backend/schemas/featureRequestSchema.js—FeatureRequestCreateSchema(title/description),FeatureRequestUpdateSchema(partial — at least one of title/description required, an empty body rejected before it ever reaches the atomic ownership+status-guarded service call),FeatureRequestStatusUpdateSchema(deliberately a restricted enum —planned/in_progress/shipped/declinedonly;"open"is never a valid transition target and"withdrawn"is self-service-only, not reachable through the admin endpoint at all),FeatureRequestRetrySchema(optional boundedlimit, same shape as Contribution's).backend/schemas/featureRequestSchema.test.js— 20 tests.backend/controllers/featureRequestController.js— student-facing (any authenticated role, no role gate beyondrequireAuth):submitFeatureRequest(POST /api/feature-requests, 201),listFeatureRequestsPublic(GET /api/feature-requests, the public board — hydrates each row'shasVotedfor the calling user via Batch 1'sgetVotedRequestIds()in one extra query, not N),getMyFeatureRequestsController(GET /api/feature-requests/mine, always scoped toreq.userDoc._id, same "never a client-supplied id" posture every other "mine" endpoint in this codebase already takes),voteFeatureRequestController(POST /api/feature-requests/:id/vote, the toggle),editFeatureRequestController(PATCH /api/feature-requests/:id, 409 on not-found/not-owner/not-open — not further distinguished, same postureapproveContributionAdmin's 409 already takes, so the response can't be used to probe whether a given:idexists at all if it isn't the caller's),withdrawFeatureRequestController(POST /api/feature-requests/:id/withdraw, same guard shape).backend/controllers/featureRequestController.test.js— 14 tests.backend/controllers/adminFeatureRequestController.js— admin-facing:listFeatureRequestsAdmin(GET /api/admin/feature-requests— goes straight to theFeatureRequestmodel, not through Batch 1'slistFeatureRequests()service function, since that one is built for the public board's "exclude withdrawn by default" semantics and the admin console needs the opposite — every status visible at a glance, same "admin listing bypasses service-layer indirection for a plain read" postureadminRewardStoreController.jsalready established for catalog CRUD; no default status filter, unlike Contribution's queue defaulting topending, since most of this lifecycle — planned/ in_progress — isn't a one-shot review action the way Contribution's is),updateFeatureRequestStatusAdmin(POST /api/admin/feature-requests/:id/status, 409 on not-found-or-terminal, triggers Batch 1's reward-issuance attempt on reachingshipped),retryFeatureRequestRewardsAdmin(mirrorsretryContributionRewardsAdminexactly). Both mutating actions callrecordAdminAction()on success only, same convention every other admin controller in this codebase already follows.backend/controllers/adminFeatureRequestController.test.js— 9 tests.backend/routes/featureRequests.js—POST /,GET /,GET /mine,POST /:id/vote,PATCH /:id,POST /:id/withdraw.
backend/routes/admin.js— added the Feature Requests import block and three routes (GET /feature-requests,POST /feature-requests/:id/status,POST /feature-requests/retry-rewards), placed directly after the Rewards Store block, matching that section's own comment style.backend/server.js— importedroutes/featureRequests.jsand mounted it atapp.use("/api/feature-requests", requireAuth, apiLimiter, featureRequestRoutes), directly after the/api/reward-storemount, same auth/rate-limit shape as every other authenticated route in that block.
node --checkon every new/modified file: ✅ clean.npx eslinton every new/modified file: ✅ clean, zero findings.- Dry ESM import of
routes/admin.jsandroutes/featureRequests.js(catches import-resolution errorsnode --checkcan't): ✅ clean. - Full backend unit suite re-run: ✅ 1126/1126 passing, 101/101 files (1083/98 Batch-1 baseline + 43 new tests: 20 schema + 14 student controller + 9 admin controller).
- Frontend: not touched this session — Batch 2 is backend only, same posture Contribution's own Batch 2 took. No frontend suite re-run needed.
- Real-Mongo integration tier — the vote race (concurrent vote/vote, vote/unvote) still hasn't been verified against real Mongo atomicity, only reasoned through and covered by mocked unit tests. Batch 3, not started, same as flagged after Batch 1.
- Frontend — no submission form, public board, or admin status-management page yet. Batch 4, not started.
reconcileVoteCount()self-heal — still just proposed in the scoping doc, not built. Infrastructure-only follow-up, not a blocker.
Bunny's explicit instruction: continue to Batch 3. Same motivation
Contribution's own Batch 3 had — the vote race (this phase's genuinely
new concurrency-sensitive piece, absent from Contribution entirely) is
exactly the kind of thing mocked unit tests can't actually verify:
mocks don't enforce MongoDB's own unique-index atomicity or a real
findOneAndDelete/findOneAndUpdate race, only a real database can.
backend/services/featureRequests.workflow.integration.test.js— 17 tests, mirrorscontribution.workflow.integration.test.js's structure andtest/mongoMemoryServer.jsharness exactly (samestartTestMongo/clearTestMongo/stopTestMongolifecycle, same real-User/real-RewardLedgerassertions, no mocked models). Threedescribeblocks:- Basic workflow (9 tests) — Counter.js-backed sequential
ccIdallocation (FR/001,FR/002, ...), the submitter's own auto-vote on create, a second user's vote/unvote persisting realFeatureRequestVoterows, edit/withdraw's atomic ownership+status guard (a stranger's attempt genuinely fails against a real query, not just a mocked return value), the fullopen → planned → in_progress → shippedtransition issuing a real, configuredRewardLedgerentry, and confirmingdeclinednever issues one and a terminal request can never be re-transitioned. - Vote race (4 tests) — the actual point of this batch:
- Two different users voting concurrently: both land,
voteCountincrements by exactly 2 (proves the$incisn't losing an update under real concurrency). - The same user attempting to vote twice concurrently (no existing
vote): exactly one
FeatureRequestVoterow persists — proven against the model's real unique index, which a mock cannot enforce at all. - The same user's vote/unvote racing against itself — this is the
one race
toggleVote()'s own header comment already flags as not fully closed (a genuinefindOneAndDelete-then-createTOCTOU across two separate calls, not one atomic operation). This test doesn't assert a specific end state (the outcome is genuinely non-deterministic) — it asserts the failure mode stays benign:voteCountalways matches the real row count (no silent drift), never goes negative, and the unique index still prevents more than one row for that(featureRequestId, userId)pair even under this adversarial interleaving. Confirms the documented limitation is exactly as narrow as claimed, not silently worse. - A vote and an admin status transition racing on the same request (different fields, no actual conflict) — both land correctly, confirming they're genuinely independent writes.
- Two different users voting concurrently: both land,
- Reward failure recovery (4 tests) — mirrors
retryPendingContributionRewards()'s own integration coverage:skipped_unconfigured→issuedon retry, no double-issue on a repeated retry call,limitleaving a correctly-sized remainder, and confirming adeclinedrequest never enters the retry queue at all (it's nevershipped, sorewardStatusnever leaves"pending"for it).
- Basic workflow (9 tests) — Counter.js-backed sequential
node --check: ✅ clean.npx eslint: ✅ clean, zero findings.- Confirmed the new file does not affect
npm test(unit suite, separate config,**/*.test.jsonly, explicitly excluding**/*.integration.test.js) — reran full suite: still 1126/1126, 101/101 files, identical to before this batch. - Attempted
npm run test:integrationagainst just this file: fails with the exact samefastdl.mongodb.org403 every other integration tier in this repo already hits (test/README.md's known sandbox restriction) — same posture as the Referral and Contribution tiers: reasoned through against Mongoose/Mongo's documented atomicity semantics and against those tiers' own already-proven patterns, but not confirmed by an actual run in this sandbox. Needs your local machine or real CI, same outstanding item those two tiers already carry.
- Frontend — no submission form, public board, or admin status-management page yet. Batch 4, not started.
reconcileVoteCount()self-heal — still just proposed in the scoping doc, not built. Infrastructure-only follow-up, not a blocker.- An actual confirmed run of this integration tier — same sandbox limitation as Referral's and Contribution's own integration tiers; added to the "Outstanding" list below rather than silently left only in this section.
Bunny's explicit instruction: continue to Batch 4 — the last batch of this phase. Full end-to-end feature now exists, backend and frontend, both roles, same "plan first, implement in confirmed batches" arc every other phase in this codebase has followed.
Followed the closest existing analogs rather than inventing new UI
patterns: RewardsStorePage.jsx for the student-facing two-tab
("Board" / "My Requests") shell (this feature genuinely has the same
"browse a public catalog vs. see my own history" shape Rewards Store
does, more than Contribution's single-page form-plus-history layout),
AdminContributionsPage.jsx for the admin console's status-tabs/toast/
busyId pattern and the submission/edit overlay-panel convention. Not
run through the frontend-design skill for the same reason Batch 5 of
Contribution Infrastructure wasn't — this is functional UI inside an
already-established design system, and matching that system exactly is
the correct choice here, not a missed opportunity for a distinctive
visual identity.
AdminContributionsPage.jsx's Approve/Reject is a fixed pair — every
row gets the same two buttons. A FeatureRequest has a real multi-stage,
non-terminal lifecycle (open → planned → in_progress → shipped, with
declined reachable from open/planned only), so a fixed button pair
would be wrong on most rows (e.g. showing "Ship" as an option on a row
still open). Each row's admin actions are instead derived from a
NEXT_ACTIONS lookup keyed by the row's current status, sourced
directly from backend/schemas/featureRequestSchema.js's own
admin-settable enum — shipped/declined/withdrawn rows correctly
get no actions at all (terminal, matches the backend's own guard).
src/services/featureRequestApi.js— thinapiFetch()client, both student and admin functions in one file, same precedentcontributionApi.jsandrewardStoreApi.jsalready established (there is no logged-out half of this feature — every backend route requires auth).src/pages/FeatureRequestsPage.jsx— student/recruiter/TPO-facing (any authenticated role, no role gate — matches the backend). Two tabs: "Board" (the public list, sorted Top/New, each row a vote button + status badge) and "My Requests" (own history, withdrawn included, with Edit/Withdraw actions on rows stillopen). A "New request" button opens a submission panel; editing opens the same panel shape pre-filled. Both panels are the same overlay/modal conventionAdminContributionsPage.jsx's reject-reason modal andRewardsStorePage.jsx's shipping-address panel already established — not a new pattern. The board's vote button shows filled vs. outline based on the backend'shasVoted(hydrated server-side bylistFeatureRequestsPublic'sgetVotedRequestIds()call, Batch 2) — no separate client-side "did I vote" tracking needed.src/pages/admin/AdminFeatureRequestsPage.jsx— admin-facing status console. Status tabs (Open/Planned/In progress/Shipped/Declined/Withdrawn/All), a "Retry rewards" button (mirrorsAdminContributionsPage.jsx's exactly), and theNEXT_ACTIONS-driven per-row action buttons described above. Ship's toast differentiatesrewardStatusoutcomes (issued / skipped_unconfigured / failed), same conventionAdminContributionsPage.jsx's own approve-toast already uses.
src/App.jsx— lazy-loaded both new pages; registered/feature-requests(student-facing,ProtectedRoute+ThemeGate, same guard shape as/contribute//credits//rewards-store) and/admin/feature-requests(nested under the existing/adminRoleRouteguard, no new guard logic needed).src/layouts/AdminLayout.jsx— added a "Feature Requests" nav entry (Platform group,Lightbulbicon) and itsPAGE_TITLESentry.src/pages/ClubPage.jsx— added a "Feature Requests" card in the same visual slot/style as the existing Contribute/Credits/Rewards Store cards, so the feature is discoverable rather than only reachable by typing the URL.
npx eslinton every new/modified file: ✅ clean on the first pass — no findings needed fixing this time (unlike several prior batches in this codebase, which each caught at least onereact-hooksviolation or unused import along the way).npm run lint(full-repo delta): only the same pre-existingCollegeDetailDrawer.jsxitem every phase in this tracker has carried forward — confirms zero new lint debt introduced.npx vitest run: ✅ 394/394 passing, 63/63 files, unchanged — no new component tests added for either new page, matching every other page added across Phases 2F/3/4 in this codebase (none of them have page-level component tests either — this codebase unit-tests services/utils, not pages).npm run build: ✅ succeeds. Confirmed all three new files code-split into their own lazy chunks (FeatureRequestsPage-*.js,AdminFeatureRequestsPage-*.js,featureRequestApi-*.js) rather than bloating the main bundle.
Every piece from the scoping doc
(plans/005-feature-requests-scoping.md) is built and wired, both
sides, both roles: submit, vote, edit/withdraw, admin status management,
reward-on-ship, and the public board. Two things remain, both explicitly
non-blocking follow-ups rather than gaps in what was asked for:
- This phase's integration tier has never actually been run — same
fastdl.mongodb.orgsandbox restriction the Referral and Contribution tiers already carry as open items. Listed in "Outstanding" below. Particular attention is worth paying to the three vote-race tests once a real run is possible. reconcileVoteCount()self-heal isn't built — proposed in the scoping doc for the same class of driftreconcileCreditsBalance()already handles for Credits, still not written. Infrastructure-only follow-up, same "flagged, not silently skipped" posture every other deferred self-heal in this codebase already has.
Neither blocks the feature from working correctly for the overwhelming common case (no crash mid-vote-toggle, an admin actually manages the board) — the same "hardening/ops follow-up any shipped feature here carries at first-ship time" posture Phase 4's own first-ship state had.
Bunny's explicit instruction: start Phase 6. Scoping doc written first
(plans/010-language-expansion-scoping.md — see the session note above
for why it's numbered 010, not 006). Two decisions surfaced rather than
inferred: which language first, and how to backfill starter code across
the existing catalog (250 problems — corrected from an initial
mis-scoping of 771, which was actually the total *.json file count
across meta/testcases/hints, not the problem count).
Bunny's first instinct (C) was tested against the actual codebase before
committing to it: every existing Java/C++ starter is a class Solution
OOP wrapper, and C has no classes, so a C driver needs a genuinely
different free-function convention (explicit array-length params,
malloc'd out-params) — not a mechanical port of the C++ driver-gen logic
as originally assumed. Flagged to Bunny directly; instruction was "make
sure users should not feel inconvenience," which pointed at TypeScript
instead — a structural superset of JS that could ship with full,
mechanical, zero-gap starter-code coverage across the whole catalog
immediately, unlike a hand-authored C backfill would have.
Full batch detail (files touched, the format-mismatch bug the backfill
script's own safety check caught before writing anything, the stale
compiler.test.js assumption found and fixed, verification numbers) is
in plans/010-language-expansion-scoping.md's "Decisions made" /
"Batch 1" sections — not re-transcribed here a second time, same
posture this file already takes toward Phase 5's own detail.
- Backend
npx vitest run: 101/101 files, 1131/1131 tests (1126 baseline + 4 new TypeScript driver-gen tests + 1 new disabled-language rejection test). - Frontend
npx vitest run: 63/63, 394/394, unchanged. npx eslint .(full repo): same single pre-existingCollegeDetailDrawer.jsxfinding, zero new lint debt.node --checkon every touched file;npm run buildsucceeds.node backend/scripts/checkProblemsFolderDrift.js: zero drift, 250/250 problems now carry astarter/typescript.tsfile matchingsrc/data/problems.js.
enabled: falsestill stands. Judge0 ID 74 is the well-known Judge0 CE id for TypeScript 3.7.4 but has not been confirmed against this deployment's actual Judge0 instance — needs that confirmation before flipping the switch (seelanguages.js's own 4-step header on what "flipenabled: true" actually unlocks).- No live-Mongo exercise of
importProblems.js's new optional-read path — lint/node --checkclean, but not run against a real database import this session. - Frontend changes — none needed yet;
GET /api/languagesalready excludes disabled languages by construction, so nothing to wire untilenabledflips. - Not started: any second/third language, and the
starterCodefixed-field-vs-map schema question the scoping doc flagged as worth revisiting once more than one new language exists.
Before continuing language scaling, Bunny provided the original architecture spec for the Content & Execution Architecture work already implemented in prior phases. Audited the live codebase against every section of it rather than assuming the prior phases' completion claims still held. Verdict: mostly correctly built and hardened -- hidden- testcase exclusion verified across every read path, fail-closed grading confirmed, config-vs-database split matches the spec's own reasoning, indexes match actual filter fields, Redis cache with explicit invalidation confirmed. Full section-by-section detail is in the conversation log, not re-transcribed here.
Two real gaps found and fixed (detail in
plans/010-language-expansion-scoping.md's own "Cross-check" section):
- No problem versioning existed. Added
Problem.contentVersion(minimum-viable -- bumped only on grading-contract-affecting field changes, via both apre("save")and apre("findOneAndUpdate")hook) andSubmission.problemVersion(captured at judge time). - Catalog problems (250/257) had no way to toggle
hiddenTestcaseSet.enabledvia the admin API -- only fully admin-authored problems could. Extended the catalog safelist withhiddenTestcaseSetEnabled, confirmed safe against both writers (seedProblems.js,importProblems.jsalready preserve the toggle on reseed).
Verification: backend 101/101 files, 1136/1136 tests (1131 baseline +
5 new unit tests). New real-Mongo integration test
(models/Problem.contentVersion.integration.test.js, 12 cases) written
but not runnable in this sandbox -- same fastdl.mongodb.org block
as the rest of this repo's integration tier; confirmed correctly
excluded from npm test and correctly picked up by
vitest.integration.config.js. Lint + node --check clean on every
touched file. Frontend suite/build unaffected (no frontend files
touched).
Not done this session: no admin UI to surface either
hiddenTestcaseSetEnabled or contentVersion (backend-only, reachable
via the existing PATCH /api/admin/problems/:slug endpoint); no
backfill of contentVersion for problems whose grading contract changed
before this field existed -- every problem starts at 1 regardless of
real edit history.
Bunny ran the integration tier in real CI (mongodb-memory-server isn't reachable from this sandbox, so this tier could only be written, never executed, until now). It caught two genuine bugs:
- Both
contentVersionhooks threwTypeError: next is not a functionon every single.save()/findOneAndUpdate()call -- 34 of the 37 failures. Root cause: this project runs Mongoose 9, which dropped legacy callback-style (function (next) { ... next(); }) middleware support entirely. The hooks were written in that legacy style. Fixed by rewriting both as pure promise-style hooks (nonextparameter, resolve by returning) -- confirmed this matches the one other pre-save hook already in this codebase (User.js'ssetEmailDomain, which declares an unusednextparam but never calls it, and passes in CI). This bug reached CI because the unit-tier tests (which mock the Mongoose model entirely) can't exercise real middleware -- only the integration tier, which needed a real Mongo connection this sandbox never had, could have caught it. - The new integration test's own fixture was missing a required
field (
description) -- 8 of the 37 failures, all inProblem.contentVersion.integration.test.jsitself. Not a production bug, a test-authoring gap:baseProblem()didn't include every fieldProblem.jsrequires. Fixed by adding it and cross-checking programmatically against everyrequired: truefield in the schema (id/title/slug/functionName/difficulty/topic/ description) to make sure nothing else was missed this time.
Both fixes are code-review-verifiable (lint + node --check clean,
full unit suite still 101/101 files / 1136/1136 tests) but the
integration tier itself still needs a real CI run to confirm -- this
fix has NOT been executed against a real Mongo from this environment,
same limitation as every other integration test here.
Context: Bunny reported the Black & White Mode toggle "not sure it was implemented fully" and asked for an audit + fix, explicitly scoped to only the toggle (no other product changes). This feature was never logged in PROGRESS.md before this session — no prior phase entry exists for it, so this audit had no tracker claims to verify against, only the live code.
The toggle's core infrastructure was solid: BWModeContext/useBWMode/
bwModeStorage (persistence, pre-paint inline script to avoid a flash of
the wrong theme), and semantic tokens (--background, --foreground,
--surface, --surface-elevated, --border, --border-strong,
--muted-foreground) toggled via an html.bw-mode class in index.css.
207 of 284 .jsx files already consumed these tokens correctly.
The gap: 56 files bypassed the tokens with hardcoded Tailwind dark
classes (bg-zinc-900, text-white, etc.) that never reacted to the
toggle. The worst of it was exactly the area Bunny was worried about —
the problem workspace / test-results path:
TestcaseResultPanel.jsx(the literal testcase Input/Expected/Actual display) used a second, entirely separate static color system (bg-ink-900/950, defined as static hex values in index.css's@themeblock, not CSS custom properties) — invisible to a first-pass grep forzinc-/gray-/etc. Only found by grepping forink-specifically after noticing the pattern in one file.ProblemEditor.jsxandInterviewModePage.jsxboth hardcoded the Monaco editor itself totheme="vs-dark"— the actual code being typed never switched theme even though the chrome around it did (or, pre-fix, didn't either).ProblemLayout.jsx(the full-page wrapper for every problem page) had its entire<body>-level background and header hardcoded dark.
Full problem-workspace chain: ProblemLayout, ProblemEditor (+ Monaco
theme), TestcaseResultPanel, WorkspacePanel, SubmitResultCard,
SubmissionHistory, SubmissionDetailsModal, ProblemWorkspaceLayout,
MobileTabBar, EditorMoreMenu, the submission-experience modals
(NextBestProblemCard, ReflectionPrompt, SubmissionCelebrationModal),
the live SubmissionResultBanner (workspace/, not the unused
problem/workspace/ duplicate — see below). Plus: shared Button,
ContactChannels, RecommendationSection, PublicProfileCard,
InterviewModePage (+ its own Monaco instance), ThemeSelectionPage,
ContestsPage, CandidateTestsPage, LandingFooter, QuizResultModal,
DailyQuizGuard/DailyQuizGate (full-screen quiz gate, seen by every
student), and App.jsx's route-transition loading screen.
ProblemEditor.test.jsx needed a matching update: mocked useBWMode
the same way the file already mocks useTheme, since the component now
calls it and the test wasn't wrapped in a provider.
- Fixed-dark "terminal readout" surfaces —
HeroTerminal.jsx,ShareCardCanvas.jsx(a downloadable branded share-card image, not a live UI surface), and the small diff/error output chips insideSubmitResultCard/SubmissionDetailsModal/WorkspacePanel. These use light-on-dark status colors (green-300/red-300/amber-500) that would fail contrast on a white background — flipping them would make things less readable, not more. This matches an existing, documented precedent already inindex.cssforHeroTerminal/Hero ProofCard. - Modal backdrop scrims (
bg-black/NNon ~20 files:ConfirmDialog,CommandPalette,SideDrawer, most feature modals, admin pages) — a standard semi-transparent overlay pattern, not a themed surface. BottomWorkspaceTabs.jsx— confirmed dead code (not imported anywhere; superseded byWorkspacePanel's own built-in tab bar). Left as-is rather than editing unused code.src/components/problem/workspace/SubmissionResultBanner.jsx— a second, unused duplicate of the livesrc/components/workspace/version. Flagged here for a future cleanup pass (not touched, since deleting/consolidating files wasn't in scope for this fix and it has zero runtime effect either way).
eslinton all touched files: cleanvitest run: 64/64 test files, 400/400 tests passingnpm run build: succeeds- Diff scoped to exactly the files listed above —
package-lock.jsonchurn from runningnpm installlocally was reverted before handoff, per Bunny's explicit "only Black & White Mode, nothing else touched" constraint.
- Deleting or consolidating the dead
problem/workspace/ SubmissionResultBanner.jsxduplicate — flagged, not actioned. - No further systemic sweep beyond the two greps used here
(
zinc-9/8|gray-9/8|slate-9/8|neutral-9/8|blackandink-) — if a third hardcoded-color convention exists somewhere in the codebase under a different naming scheme, it wasn't caught by this pass.
C's registry entry, languageTypes/c.js, and languageDrivers/c.js were
already wired in from a prior session (enabled: false, judge0Id: 50
unverified) — this session's job was actual starterCode.c content, per
plans/012-c-starter-backfill-scoping.md. Scoping doc approved with
three explicit decisions: (Finding 2) do NOT extend the driver yet, only
backfill shapes it already supports; one batch per session; long-tail
problems get best-judgment signatures with reasoning documented, not
per-problem sign-off.
Audited the real catalog rather than trusting the scoping doc's
projected ~90-problem estimate: 80 problems actually matched the five
target shapes (arr1d->int, arr1d,num->int, num->int, arr1d->bool,
str,str->bool); 72 backfilled, 8 correctly excluded (ListNode*/
TreeNode* params, vector<string>&/vector<char>& params, uint32_t
return/param) rather than forced. Every accepted problem's C signature
was cross-checked against its actual declared C++ signature, not
guessed from a testcase value's structural shape alone — this is what
caught two-sum-count-pairs needing long long (extended its existing
returnType: { java, cpp } to add c: "long long") and kept the
paramTypes.c override count at zero rather than missing a case.
validateProblemContracts.js gained C-specific checks with no java/cpp
equivalent, because languageDrivers/c.js's generate() has no generic
fallback for an unrecognized type — unlike Java/C++, it silently
mis-prints rather than erroring:
checkCReturnTypeSupported— resolves the real return-type token off the function signature first (not the driver's own lossy infer-with-default-to-"int" fallback) and rejects anything outside{int, long long, double, bool, int*, char*}.checkCArrayParamTypeSafety— flags a numeric array param containing non-integer values with no explicitparamTypes.coverride (inferCType()would silently type itint[]and truncate every value).checkC— declared-vs-actual return type mismatch, mirrorscheckJava/checkCpp.checkArgumentGenerationextended with a C branch.
7 new unit tests added to generateDriverCode.test.js covering all of
the above, including the specific "unsupported return type resolved via
inference alone would have passed" regression this check exists to
prevent.
- Backend
npx vitest run: 103/103 files, 1171/1171 tests (1145 baseline + 26 new: 7 C validation unit tests, rest incidental to the catalog data change). node backend/scripts/checkProblemsFolderDrift.js: zero drift, 72/250 problems now carry astarter/c.cfile matchingsrc/data/problems.js. (Caught and corrected an own operational mistake mid-session:exportProblemsToFolders.js/checkProblemsFolderDrift.jsresolveproblems/relative toprocess.cwd()and must be run frombackend/— ran them from the repo root once, which silently created a stray top-levelproblems/instead of updatingbackend/problems/; caught via a 0-count sanity check, deleted, redone correctly.)node backend/scripts/validateProblemContracts.js: 250 problems + 8 Code Club Edition missions, no contract mismatches.npm run lint(full repo): same single pre-existingCollegeDetailDrawer.jsxfinding, zero new lint debt.npm run build: succeeds.- Beyond static checks: compiled and executed 5 representative generated
drivers (one per shape, real implementations, not just stubs) through
actual
gcc, including thelong longoverflow case — all 5 produced correct output against real testcase data.
languageDrivers/c.js's generate() builds call-args as
Array.isArray(value) ? \${key}, ${key}Size` : key, but cDeclaration()declares${key}Rows/${key}Cols(not${key}Size`) for a 2D array —
any future 2D-array input shape would generate a call to an
undeclared variable. Relevant to whoever picks up Batch 3/5, not fixed
here per the explicit "do not extend the driver" instruction.
enabled: falsestill stands;judge0Id: 50still unverified.- The 8 skipped Batch-1-shape problems, the ~93-problem long tail
(Batch 3), the 17
operationSequence/design problems (Batch 4), and any 2D-array/string-array driver extension (possible Batch 5) — none started. plans/011-language-and-problem-extensibility.mdreconstruction — still missing from the repo, still not this session's task.
Batch 2 not started; waiting on explicit approval per the scoping doc's one-batch-per-session agreement.
Second of the one-batch-per-session sequence from
plans/012-c-starter-backfill-scoping.md. Target: the remaining
templatable shapes from the top-10 table not yet covered by Batch 1 —
arr1d->arr1d, arr1d,num->arr1d, str->int.
Scope correction made before starting, not after: re-checked
languageDrivers/c.js against languageTypes/c.js before generating
anything and confirmed the 2D-array-input bug flagged (but not fixed) in
Batch 1's report is more severe than first described — generate()'s
call-arg builder appends ${key}Size for any array regardless of
dimension, but cDeclaration() names a 2D array's companions
${key}Rows/${key}Cols. This blocks any shape with a 2D array
input, not just 2D-array output. Excluded arr2d->int (15 candidates)
and arr2d->arr2d (11) from this batch entirely — consistent with the
already-approved "don't extend the driver" decision, just applied to a
wider set than the scoping doc's table implied. Flagged to Bunny before
implementing, not after.
| Shape | Candidates | Backfilled | Skipped | Reason |
|---|---|---|---|---|
arr1d -> arr1d |
17 | 11 | 6 | 3× void return (the known rotate-array/sort-colors/next-permutation in-place-mutation bug — independently rediscovered by this batch's own analysis, not just carried over from memory), 2× string return, 1× TreeNode* param |
arr1d, num -> arr1d |
8 | 5 | 3 | 1× void return, 1× ListNode* return, 1× vector<string> return |
str -> int |
10 | 10 | 0 | — |
| Total | 35 | 26 | 9 |
Every accepted array-return problem uses the one shape
languageDrivers/c.js actually supports: int* return + trailing
int* returnSize out-param, *returnSize = 0; return NULL; as the
unimplemented-stub body (same "return empty" convention the cpp stub
already uses for these problems). No paramTypes.c overrides were
needed this batch (no double arrays or ambiguous scalars turned up among
the 26, same cross-check-against-real-cpp-signature method as Batch 1).
Cumulative C coverage after Batch 2: 98/250 (72 + 26).
- Backend
npx vitest run: 103/103 files, 1173/1173 tests (1171 baseline + 2 new: onearr1d->arr1dregression case, onestr->intcase — kept minimal since the underlyingint*-return andchar*- param validation paths were already exercised by Batch 1's tests). node backend/scripts/checkProblemsFolderDrift.js: zero drift, 98/250 problems now carrystarter/c.c(run correctly frombackend/this time, no repeat of Batch 1's cwd mistake).node backend/scripts/validateProblemContracts.js: 250 problems + 8 Code Club Edition missions, no contract mismatches.- Compiled and executed 3 representative generated drivers via
gcc(realmalloc-based array-return implementations fortwo-sumandmove-zeroes, a real DP implementation fordecode-ways) — all 3 produced correct output.
arr2d->int/arr2d->arr2d(26 combined candidates) — blocked by the 2D-array-input driver bug above, not attempted.- The known
rotate-array/sort-colors/next-permutationvoid-return bug — still just documented, not fixed (pre-existing, Java/C++-wide, out of C-onboarding scope). - The ~93-problem long tail (Batch 3), the 17
operationSequenceproblems (Batch 4), any driver extension (possible Batch 5), Plan 011 reconstruction — none started.
Batch 3 not started; waiting on explicit approval.
Third batch of the one-batch-per-session sequence. Scope: the ~93-problem long tail flagged in the original scoping doc — not template-able by the coarse JS-testcase-value shape buckets Batches 1–2 used, so this batch classified every remaining non-design, not-yet-backfilled problem (135 candidates) directly against its real cpp signature and the C driver's actual capability, per-problem, per the approved long-tail strategy ("best engineering judgment... document reasoning for non-obvious signatures... flag rather than guess").
Two capabilities used for the first time this batch, both already
present in languageDrivers/c.js/languageTypes/c.js but unexercised
by Batches 1–2's shapes: char* return (string output — generate()
has a dedicated branch, no returnSize needed) and char** parameters
(array-of-strings input — cDeclaration() already handles a 1D array
whose elements are strings via its existing char*[] branch; confirmed
generate()'s call-arg builder appends ${key}Size correctly for it
since it's structurally 1D, not the 2D Rows/Cols case). Neither
required any driver change — just recognizing the catalog had type
shapes worth mapping to them that the first two batches' input/output
buckets didn't surface.
135 candidates (non-design, not yet backfilled) → 53 accepted, 82 skipped. Skip reasons, grouped:
| Reason | Count |
|---|---|
2D return (vector<vector<int>> / vector<vector<string>>) |
18 |
void return (in-place mutation — same known bug class as rotate-array) |
9 |
| Array param is genuinely 2D in the actual testcase data | 32* |
ListNode*/TreeNode*/Node* params or returns |
8 |
vector<string> return (array-of-string return has no driver branch — char** param is fine, char** return is not) |
4 |
vector<char>& param (task-scheduler — not in the supported-type map) |
1 |
uint32_t param/return |
2 |
| Param-count / signature-parse mismatches | 3 |
| Empty/unparseable return type | 2 |
* Includes the 26 already known-excluded from Batch 2's arr2d->*
shapes — Batch 3's classifier re-derives them independently by checking
actual testcase data shape against the cpp signature (not by name),
which is why this number doesn't simply equal "82 minus Batch 1/2's
carried-forward exclusions."
One data-quality note surfaced, not fixed (not this batch's job):
binary-search-tree-iterator has real constructor+method+testcase
structure (ops/vals arrays) identical in shape to the 17
operationSequence-tagged design problems, but is not itself
flagged operationSequence.enabled: true — it was correctly excluded
here (no single free-function signature to derive a C prototype from),
but whoever owns the design-problem tagging should know it's
mis-classified.
Cumulative C coverage after Batch 3: 151/250 (72 + 26 + 53).
- Backend
npx vitest run: 103/103 files, 1175/1175 tests (1173 baseline + 2 new: onechar*-return case, onechar**-param case). node backend/scripts/checkProblemsFolderDrift.js: zero drift, 151/250 problems now carrystarter/c.c.node backend/scripts/validateProblemContracts.js: 250 problems + 8 Code Club Edition missions, no contract mismatches.- Compiled and executed 3 representative generated drivers via
gccwith real implementations (not stubs):reverse-string(char*return),longest-common-prefix(char**param +char*return),word-break(char*+char**params,boolreturn) — all 3 produced correct output.
- 2D-array shapes (input or output) — still blocked by the
${key}Size-vs-${key}Rows/${key}Colsdriver bug flagged in Batch 2's report, unfixed. -
vector<string>return (array-of-string return) — no driver branch exists;char**as a param is now supported, as a return type is not. -
ListNode*/TreeNode*problems — need real C struct definitions, driver-level work. - The known
void-return in-place-mutation bug (9 more instances of the same class already documented forrotate-array/sort-colors/next-permutation) — still just documented. - The 17
operationSequence/design problems (Batch 4) — still not started, including the mis-taggedbinary-search-tree-iteratornoted above. - Any driver extension (possible Batch 5), Plan 011 reconstruction — still not started.
After three batches, 99/250 problems remain without starterCode.c:
17 design problems (Batch 4) + 82 driver-blocked or otherwise-excluded
(2D shapes, void-return bug, pointer-struct params, array-of-string
return — real remaining work, not oversights).
Phase 6 — Language Expansion, Plan 012 Batch 4: C starter-code backfill, design/operationSequence problems (this session)
Fourth batch — the 17 operationSequence (design) problems this whole
sequence has deferred since Batch 1. Result is smaller than any prior
batch, for a real reason discovered mid-batch, not a shortfall in effort.
Two driver bugs found before writing any starter code, both confirmed against real compiled/run C, not just read from source
-
void-return compile failure.generateOperationSequence()unconditionally emits_results[i] = (long) ClassName_method(...)for every call in the sequence, with no void-detection (documented as a known gap in the driver's own header comment, but its actual blast radius hadn't been measured until now). Casting avoidexpression tolongis a hard C compile error — confirmed with a minimal repro (gcc: "invalid use of void expression") and then again with a full, correctLRUCachestruct-based implementation to rule out "maybe a real implementation avoids it" — it doesn't; the bug is in the driver template itself, unrelated to what the student writes. 14 of the 17 design problems have at least one void method actually invoked in their op sequence (put,addNum,insert,push,follow,set, etc.) and are blocked by this. -
bool-return grading failure — found only after compiling and running a real implementation, not from reading the code. Results are always printed with%ld, so abool-returning method prints1/0.expectedOutputfor these problems is authored as JSONtrue/false, andjudgeController.js'soutputsMatch()does an exactJSON.parse-based array comparison — confirmed directly (JSON.stringify(JSON.parse("[1,1,0]")) !== JSON.stringify([true,true,false])) that this fails grading outright, not just formatting. This blocked 2 of the 3 problems that were otherwise clear of bug #1 (my-calendar-ii,design-circular-queue— both haveboolmethods) — theirstarterCode.cwas written, verified compiling, and then removed again once this second issue surfaced, rather than shipped with a known grading defect.design-twitterwas excluded for a third, independent reason:getNewsFeedreturnsvector<int>, not a scalar at all.
Net: 1 of 17 backfilled (online-stock-span — the only one of the
17 whose every method returns a plain int, clear of both bugs).
validateProblemContracts.js gained checkOperationSequenceCSupported
— for any operationSequence problem carrying a C starter, parses the
cpp signature's method return types and rejects void (bug #1) and
bool (bug #2) explicitly, with a message naming which method and
which failure class. This exists specifically so a future session can't
accidentally re-add starterCode.c for a void/bool-method design
problem without hitting the exact same wall immediately, in the
validator, rather than after a real submission fails. 3 new unit tests
added (void-method rejection, bool-method rejection, all-int-method
pass) — generateDriverCode.test.js now at 42 tests total.
Cumulative C coverage after Batch 4: 152/250 (72 + 26 + 53 + 1).
- Backend
npx vitest run: 103/103 files, 1178/1178 tests. node backend/scripts/checkProblemsFolderDrift.js: zero drift (caught and fixed one real gap in this session's own process — the folder mirror wasn't regenerated between adding all 3 candidates and reverting 2 of them, which the drift-check test file itself caught).node backend/scripts/validateProblemContracts.js: 250 problems + 8 Code Club Edition missions, no contract mismatches.- Compiled and ran real (non-stub) implementations of all 3 originally-
attempted problems through the actual
generateOperationSequenceDriver— this is what surfaced bug #2 in the first place;online-stock-span's real implementation produces byte-correct output, the other two's real implementations compile and run but print1/0wheretrue/falseis expected, confirming the revert decision rather than assuming it.
- The
void-cast bug — affects 14 of 17 design problems, not fixed (real driver-template work, same "don't extend the driver" boundary every prior batch has held to). - The
bool-print-format bug — affectsmy-calendar-iianddesign-circular-queuespecifically among the otherwise-clear problems, not fixed. design-twitter'svector<int>-returning method — a third, independent blocker, not fixed.- Any driver extension (possible Batch 5), Plan 011 reconstruction — still not started.
After four batches: 152/250 problems have starterCode.c. 98
remain: 16 design problems (blocked by bug #1 and/or #2 above) + 82
from Batch 3's driver-blocked/excluded set (2D shapes, void-return
mutation bug, pointer-struct problems, array-of-string returns). Fixing
either design-problem bug would very likely unlock most of the
remaining 16 — worth scoping as a real Batch 5 (driver work), not
another content-authoring batch.
Real driver work, not content authoring — the two bugs Batch 4 found and
documented (void-cast compile failure, bool-print grading failure)
are fixed in languageDrivers/c.js's generateOperationSequence().
C has neither Java's reflection nor C++'s SFINAE/decltype to detect a
method's return type generically — but every op call's target method is
already known BY NAME at driver-generation time (this function runs
in Node with the full starter-code string available), so the fix reads
each method's real return type straight off its own signature line in
the starter code (same anchored-regex approach checkC/
checkCReturnTypeSupported already use), then generates different code
per call depending on what that type actually is:
void→ called with no result variable; contributes anullentry only whenresultMode === "all"(previouslyresultModewasn't even destructured from the function's parameters — silently ignored).bool→ printstrue/falsedirectly, not cast throughlong.int/long/long long/double→ each cast and printed with its own correct format specifier (mirrorsgenerate()'s existing single-call print-format mapping, applied per-method instead of once).char*→ prints as a quoted JSON string (new capability — no operation-sequence problem in the catalog used this before).- Anything else (array, struct pointer) → throws at generation time,
replacing the old unconditional-cast-to-
longfallback that silently produced wrong output for exactly this case. Closes the bug class Batch 4 found, doesn't just patch the two specific instances of it.
Structurally this also simplifies the generated driver: the old
homogeneous long _results[] buffer + uniform %ld print loop is gone,
replaced by direct incremental printf at each call site (mirrors how
generate()'s single-call driver already prints directly rather than
buffering) — there was never a real need for a results array once
results can be heterogeneous types.
15 of 16 were unlocked by the fix and backfilled:
lru-cache, find-median-from-data-stream (first double-returning
design problem), time-based-key-value-store (first char*-returning
design problem — proves the new string-return path), implement-trie,
design-add-search-words, implement-queue-using-stacks,
my-calendar-ii, implement-stack-using-queues, design-hashmap,
design-circular-queue, maximum-frequency-stack,
two-sum-iii-data-structure, implement-trie-ii, map-sum-pairs,
minimum-stack.
design-twitter remains excluded — getNewsFeed() returns
vector<int>, a genuinely non-scalar/non-string type this fix doesn't
address (never was a void/bool problem; a different, still-real gap).
validateProblemContracts.js's checkOperationSequenceCSupported was
updated to match — it no longer rejects void/bool, only what the
driver genuinely still can't represent (array/struct-pointer method
results). 2 stale Batch 4 tests (asserting the now-fixed rejection
behavior) were updated to assert the new, correct pass-through instead;
1 new test added covering the real remaining blocker
(vector<int>, matching design-twitter's actual case) — net change
to generateDriverCode.test.js is +1 test. operationSequenceDriver.test.js's
structural C tests and its real-gcc-compiled end-to-end test were
rewritten to match the new generated-code shape and correct expected
output ([null, null, null, -3, null, 0, -2], matching Python's and
TypeScript's own real semantics for the same MinStack sequence —
previously asserted [0, 0, 0, -3, 0, 0, -2], the old bug's signature).
Cumulative C coverage after Batch 5: 167/250 (72 + 26 + 53 + 1 + 15).
- Backend
npx vitest run: 103/103 files, 1181/1181 tests. node backend/scripts/checkProblemsFolderDrift.js: zero drift, 167/250 problems now carrystarter/c.c.node backend/scripts/validateProblemContracts.js: 250 problems + 8 Code Club Edition missions, no contract mismatches.- Compiled and ran real (non-stub, non-synthetic) implementations
through the actual fixed driver for 4 of the 15:
lru-cache(resultMode: "returningOnly"— confirms voidput()calls are correctly omitted, not just null-padded),time-based-key-value-store(char*return),find-median-from-data-stream(doublereturn), and the pre-existingminimum-stackend-to-end test (resultMode: "all"— confirms void calls correctly printnull). All correct.
-
design-twitter'svector<int>return — different, still-real gap, not this fix's job. - The 2D-array-input bug (
${key}Sizevs${key}Rows/${key}Cols) flagged in Batch 2 — separate from the operationSequence driver entirely (that's the single-callgenerate()path), still open. - The ~93→82-problem-and-shrinking long-tail exclusions from Batch 3
(array-of-string returns,
ListNode*/TreeNode*, the knownvoid-return in-place-mutation bug for regular (non-design) problems) — unrelated to this batch's fix, still open. - Plan 011 reconstruction — still not started.
After five batches: 167/250 problems have starterCode.c. 83
remain, all previously-documented and still real: design-twitter (1),
82 from Batch 3's driver-blocked/excluded set.