Title page date
Now "October 28, 2026".
Team Relay · CIS*3250 A3 report · due tonight, Wed Oct 28, 11:59 PM · updated Oct 28, 9:30 PM (checked against the 9:26 PM PDF)
How to read this. The number on each person's button is how many items they still have to do.
Items marked Owner must do are new analysis, new evidence, or your own Agent Appendix, so only you can write them.
Gurmehar applied the corrections (wrong line numbers, typos, sentences the code contradicts) and, in some sections, one to five new sentences;
each is quoted word for word under your name in "Handled by Gurmehar" and tagged Fixed. You don't need to do anything for those, but check you're happy with the wording.
The report's Limitations of AI Assistance paragraph declares them.
Priority: Critical loses a mark or is an integrity issue, Important a grader checking the code would catch, Polish if there's time.
Every item was checked against the code (Meridian e95dc51, jq jq-1.7.1) and recorded runs of Meridian. Ticks save in your own browser only.
These affect several sections at once.
Validate.requireStation and QueryBuilder.whereEq, as SummaryHandler.java:60-61 does), and do MRD-77 later. Suggested order: baseline, MRD-166, MRD-247, MRD-249, MRD-248, MRD-77. (Sonia's order of work now matches.)Env.java. It stays inside our subsystem and matches tests T13 and T24.Dispatcher.java:30-34, written in the same 2026 commit (a0f1aef) as the cache bug, says it is "knowingly a second way of doing what Router already does". Suggested wording: the 2026 code flags its deliberate choices, but not its defects./api/forecast?station=X'OR'1'='1 returns 200 with all six stations' forecasts;
/api/series?station=X'OR'1'='1&year=2025 returns all 1,044 daily rows for every station;
the newer /api/summary rejects the same input with 400 not a station id.Title page, headings, final paragraph · Gurmehar
Now "October 28, 2026".
The cut-off sentence is finished and "ChatGPT" becomes "the AI tools each of us declared above". It now ends with the team-wide declaration of the final review:
“A final review of the whole report with Claude Code (Claude Opus 5.5) checked every section against the code and the recorded runs, and Gurmehar applied the corrections and short additions it proposed across the sections. It also drafted the jq evidence for Signature 1 and the matching sentences in its comparison.”
Now "Related ticket: MRD-77".
"Part 2; Signature 3" becomes "Part 2, Signature 3"; Jason's figure becomes Figure 3.
Part 1, Genuine pattern (QueryBuilder)
Strong section: the pattern is named precisely, the 13 call sites in 8 files and the f7c4b94 history check out, and both diagrams are readable.
Page 28 shows "Used ... :" and five blank headings. The brief says undeclared AI use is an academic-integrity violation, and only you can say how you used AI. Name the tool and model, and fill all five headings: at least one thing the agent got right (e.g. the fluent Builder reading, checked at QueryBuilder.java:22, :31, :112) and one thing it got wrong or partly wrong, and how you caught it. If you used no AI for a step, say so.
It also holds a Db directly and writes fixed SQL (no user input). Optional diagram update.
The mapping table starts on page 2 and continues on page 3. Keep it with its figure (layout only).
Now "It is the only class that escapes user input for SQL". Other classes wrap user values in quotes, just not for SQL.
The Python analytics and reporting layers do use the sqlite3 library; only the Java services shell out.
"then whereEq and the other steps at :49-72" becomes "then from, whereEq and the other steps at :39-91".
The column name in each where method (:49-72) is also added raw.
Part 1, Degenerate pattern (parallel routing)
The evidence is accurate and well located. Two brief requirements are not met yet, and the figure can't be read in the PDF.
Labels are about 4 pt because the two panels sit side by side. Stack panel (a) above panel (b), each at full page width, or give the figure its own page. Check it in the exported PDF.
Reword as: "the agent's first diagram left out the duplicated route metadata, which is the core of the pattern". Item 2 says you checked the SVG labels were readable; the PDF figure isn't, so check the PDF after re-rendering.
The brief asks for the degenerate pattern to be "named precisely". Heading and opening of "Patterns and Evidence":
Was: “Parallel Routing and Duplicate Route Metadata”
Now: “Degenerate Front Controller: Parallel Routing and Duplicated Route Metadata”
Added: “Pattern: Front Controller with a route registry. Router (Router.java:52-68) registers each path passed to add() with the shared Cors and RequestLog filters and records it for the /api index, and Dispatcher (Dispatcher.java:38-153) is an annotation-driven front controller for /api/v2. Degenerate form: the registry is split across three registration styles (ApiServer.java:41-44, 60-68, 75-79), and the route metadata has become duplicated knowledge, a DRY violation: a new v2 route needs both an @Endpoint summary and a hand-written router.note() (ApiServer.java:80-82), and the two already disagree for export.”
At the start of "Recommendation":
Added: “Judgement: keep the Router and the Dispatcher, since both serve live traffic (Router.java:52-57, Dispatcher.java:132-146), and remove the duplicated metadata: delete the three hand-written router.note() lines (ApiServer.java:80-82) and build the /api index's v2 entries from Dispatcher.routes() (Dispatcher.java:108-117).”
Added: “The divergence is already visible to clients: the Dispatcher serves its own index at GET /api/v2, built from the annotations (Dispatcher.java:126-130, 166-180), so GET /api lists /api/v2/export as "CSV or JSON export of a table" while GET /api/v2 lists it as "CSV or JSON export of a table (fmt)".”
Was: “(fmt),"”
Now: “(fmt)",”
Was: “The migration of legacy routes should occur with caution and preserve the behaviour that is exposed externally by Meridian.”
Now: “The migration of legacy routes should occur with caution: it should keep their paths and JSON bodies, but moving them under the Router changes their OPTIONS answers (the Cors filter answers 204) and adds them to /api/metrics, and giving them the newer handlers' GET-only check would also turn their POST answers into 405, so those changes must be declared (Part 3, Safe change, tests T11, T12 and T17).”
Dispatcher.of() cited at :62-72 (it calls register()), Router.routes() at :65-68, Router setup at ApiServer.java:60.
"Jaoson" becomes "Jason"; adds "This pattern is tracked as MRD-248 (ISSUES.md:28), and its change is planned in Part 3, section 3.5."; your figure becomes Figure 3.
Part 2, Signature 3, Argument, Conclusion
Well written, and the core Meridian evidence (the two key() overloads) is correct. The wrong jq claims are being corrected for you; the additions below are yours.
The parse_slice and util.c claims below were wrong and are being corrected, so this line no longer holds. Replace it with a real catch, e.g. "the assistant presented the key mismatch as the whole bug; cross-checking with Part 3 showed evict runs as a separate program and cannot reach the server's cache."
Added: “Signatures 1 and 2 show a related contrast: jq's incomplete limit documentation and the redundant NaN check in jv_get are human gaps at the edges left in place, while Meridian's ForecastCache Javadoc describes an eviction that never happens and its two console copies show users different numbers (MRD-252).”
Added: “There is a second gap the ticket does not mention: the evict command runs as its own separate program with its own ForecastCache (AdminCli.java:40-42, :136), while the running server holds a different one (ApiServer.java:74), so even matching keys would not clear the server's cache.”
Was: “Catching the disconnect requires placing both private key() overloads next to each other, a step most developers wouldn't think to take without prior suspicion.”
Now: “The two private key() overloads sit back to back, separated only by a blank line and the second one's two-line comment (ForecastCache.java:131-139), each under a comment that justifies its own rule: "keyed by the station alone" (:129-130) and "the key is built from both" (:135-136). The contradiction is written in the source, but each half reads as a reasoned decision, so nothing prompts a reader to reconcile them.”
Was: “The gap likely emerged when one side was drafted around a specific concept ("key by station and year") that was never wired up to the rest of the implementation.”
Now: “git blame shows invalidate(), both key() overloads and their comments arrived together in one commit, a0f1aef (2026-07-08, the 2026 layer), so the incoherence was there from the start rather than drifting in; the evict command that relies on it arrived 13 days later in b34d9c1 (2026-07-21).”
Was: “In Meridian, there are no warning flags.”
Now: “In Meridian, the 2026 code does flag its deliberate choices (the Dispatcher's Javadoc, Dispatcher.java:30-34, from the same commit a0f1aef, calls it "knowingly a second way of doing what Router already does"), but nothing in the code flags this defect.”
Was: “neither check ensured that the store and drop routines actually agreed on the underlying contract.”
Now: “nothing checks that the store and drop routines agree on the underlying contract: services/ has no tests at all.”
Now: "a defensive fallback; every caller checks the type first (jv_aux.c:109, 118, 182-184, 280), so the branch is unreachable", and the argument sentence becomes "In jq, parse_slice keeps a branch its own comment says can never run (jv_aux.c:31-40)."
Now: "a TODO admits that stream read errors bypass the error callback, and the code falls back to printing the error directly (lines 263-265)".
Added: “For jq's maintainer, the markers and git history are a usable to-do list; for Meridian's maintainer, the cache's own Javadoc and the evict command's output cannot be trusted (ForecastCache.java:88-93, AdminCli.java:141-142), so behaviour has to be pinned by tests before anything is changed, as Part 3 does.”
Now: "its only trace is an entry count that does not drop, which nobody reads as an error."
It isn't an established pattern name.
Part 3, Changes and their reach
Contradicted by the mitigation claim and the missing SeriesHandler (both being corrected), and no ticket or file is named. Replace item 4 with a real catch (e.g. "the agent left out the SeriesHandler half of MRD-166") and give one concrete correct trace in item 3. Name the model you used.
Now: "this is not mitigated today: the newer handlers avoid it with Validate.requireStation and QueryBuilder quoting (SummaryHandler.java:45, :60), and the fix adopts these in the two old handlers."
One clause per ticket saying what the change is:
Was: “For MRD-166:”
Now: “For MRD-166 (send the station through Validate.requireStation and QueryBuilder quoting, as the newer handlers already do):”
Was: “For MRD-247:”
Now: “For MRD-247 (store and evict under the same station key, and give the evict command a path into the running server's cache):”
Was: “For MRD-248:”
Now: “For MRD-248 (register every route through one mechanism and build the /api index from it):”
Was: “For MRD-249:”
Now: “For MRD-249 (move the station catalogue out of the Java source into a configuration file read through Env.java):”
Was: “For MRD-77:”
Now: “For MRD-77 (replace the sqlite3 command-line calls in Db.java with an in-process SQLite driver):”
The brief says "all". One sentence per ticket:
Added: “It also reaches the route registration (ApiServer.java:42-43), the error path (ApiServer.java:97-106), the forecast and forecast_daily tables, and both consoles (dashboard/js/api.js:61-62, ui-next/src/api/endpoints.ts:33,42) through GET /api/forecast?station= and GET /api/series?station=&year=.”
Added: “It also reaches ForecastV2.java:94-95, ApiServer.java:74-76, the cache timeout setting meridian.v2.cache.ttl (Env.java:32, :57-58) and the admin command evict <station> <year>; because the admin tool is a separate program, it cannot reach the running server's cache.”
Added: “It also reaches the Cors and RequestLog filters, /api/metrics (the hand-wired routes are not counted today) and the /api route index (RootHandler).”
Added: “Moving it to configuration reaches Env.java and StationCache; no in-repo client calls /api/v2/stations.”
Added: “It also reaches DbClient.java:50-74, whose retry depends on the error text "exited 5", every old route that passes sqlite3's JSON through unchanged (/api/stations, /api/forecast and /api/series; the float formatting on the last two, such as "yield_t":5.95099999999999962, will change), and services/build.sh and run.sh:25, which must supply a driver and put it on the classpath.”
ForecastHandler.java:41-54 (parseQuery at :25-37) and SeriesHandler.java:22-42.ForecastCache.java:94-96 and :129-139, plus AdminCli.java:135-143.ApiServer.java:41-84, Router.java:46-63, Dispatcher.java:62-164.Db.java:29-56 and :58-75; tsstore.c marked as the ingest team's half."[Router.java] ...#L12-L48 and [Dispatcher.java](" becomes plain file:line.
Now "GitHub Copilot" in your appendix heading.
Part 3, Risks, order of work, dependencies
Your baseline facts all check out (1,044 frames, ten wire-suspect, ports, the BRANTFRD values, 13 routes), and your separate-cache-instance point in 3.4 is correct and important.
3.1, the per-ticket "Safe-Change Plan" and "Rollback Plan" blocks, and 3.8 repeat the separate Safe change section and disagree with it in places (run_at handling, database backups). Consider replacing each with one line pointing to the Safe change tests. "3.9 Conclusion" sits in the middle of Part 3.
3.1 lists seven endpoints but your appendix says six. Only you know which you ran; make them agree.
MRD-166 doesn't need MRD-77 (team decision 1). 3.2, 3.7 and 3.9 now read:
Was: “To fix this, we would replace the current string concatenation with parameterized queries.”
Now: “To fix this, we would stop pasting the raw station into the SQL: first by validating and quoting it as the newer handlers do, and later with bound parameters once MRD-77 provides them.”
Was: “This means we would need to update or extend the database interface before fully fixing the unsafe query construction.”
Now: “A full bound-parameter fix would need a new database interface, but the immediate fix reuses the validation and quoting the newer handlers already use (SummaryHandler.java:60-61), so it does not wait for MRD-77.”
Was: “If necessary, we would first introduce a smaller, tested way to bind SQL parameters and fix the vulnerable handlers.”
Now: “The codebase already has a smaller way to do this, which the newer handlers use today: they validate the station and quote it through QueryBuilder (SummaryHandler.java:60-61, QueryBuilder.java:99-110; the behaviour is recorded in Safe change T8), so we fix MRD-166 with it first.”
Was: “Step 1: Prepare safer database access related to MRD-77”
Now: “Step 1: Fix unsafe SQL queries in MRD-166”
Was: “This would help provide parameter-binding support for MRD-166.”
Now: “We would send the station through Validate.requireStation and QueryBuilder quoting, as SummaryHandler.java:60-61 already does, so the security fix does not wait for any database change.”
Was: “Step 2: Fix unsafe SQL queries in MRD-166”
Now: “Step 2: Fix cache invalidation in MRD-247”
Was: “Once a safe database interface is available, we would use it to fix the vulnerable queries.”
Now: “This would require a way for the administrative command to reach the live server's cache.”
Was: “Step 3: Fix cache invalidation in MRD-247”
Now: “Step 3: Move station metadata out of Java source code for MRD-249”
Was: “This would require a way for the administrative command to reach the live server's cache.”
Now: “We would first make sure the new metadata source is working correctly and that station queries remain compatible.”
Was: “Step 4: Move station metadata out of Java source code for MRD-249”
Now: “Step 4: Make routing more consistent for MRD-248”
Was: “We would first make sure the new metadata source is working correctly and that station queries remain compatible.”
Now: “Routing affects many endpoints, so we would leave this broader change until the other endpoint behaviours are stable.”
Was: “Step 5: Make routing more consistent for MRD-248”
Now: “Step 5: Replace the external SQLite process for MRD-77”
Was: “Routing affects many endpoints, so we would leave this broader change until the other endpoint behaviours are stable.”
Now: “This is the largest change and crosses into the ingest team's subsystem (ISSUES.md:7), so it goes last, once the characterization tests are in place.”
Each risk now names what breaks and today's recorded value:
Was: “We would test station values containing quotation marks and other SQL-special characters to make sure they cannot change the meaning of the query.”
Now: “This already happens today: station=X'OR'1'='1 returns all six stations' forecasts on /api/forecast and all 1,044 daily rows on /api/series (Safe change T5); after the fix both must answer 400 not a station id.”
Added: “For example, /api/forecast passes sqlite3's JSON through (Db.java:30), so today BRANTFRD's yield prints as 5.84100000000000019; a driver would print it differently.”
Was: “We would test these situations to make sure the API can still access the database safely.”
Now: “We would hold an exclusive lock on a copy of the database, as Safe change T27 does: today both the hand-wired /api/forecast and the Router's /api/summary answer 500 sqlite3 exited 5 (the second after two retries) and recover once the lock is released.”
Was: “We would test database failures and make sure they are handled properly.”
Now: “We would point the API at a database with no tables, as Safe change T26 does: today health answers 200 and every data route answers 500 sqlite3 exited 1.”
Added: “Safe change T22 records that today the summary still says runs 1 after evict.”
The brief says "all". Added as one sentence:
Added: “Other dependencies: MRD-166 changes the two hand-wired handlers (ApiServer.java:42-43) that MRD-248 would move under the Router, so MRD-166 lands first; MRD-249 and MRD-248 share the v2 Dispatcher path (StationsV2.java:51, ApiServer.java:75-79), so MRD-249 lands before MRD-248; MRD-77 is filed under ingest (ISSUES.md:7), changes DbClient's retry, which can recognise a lock only by the text "exited 5" because Db reports nothing but "sqlite3 exited N" (DbClient.java:67-74, Db.java:51), and needs a driver on the classpath (run.sh:25); and MRD-247's in-server eviction needs a path from the admin command into the running server, such as an authenticated admin operation, which services does not have today.”
Was: “while keeping the existing endpoint paths and responses the same.”
Now: “while keeping the existing endpoint paths and JSON bodies the same. Moving the hand-wired routes under the Router would change their OPTIONS answer (200 today, 204 after) and add them to /api/metrics, and giving them the GET check the newer handlers already make (SummaryHandler.java:41-43) would change their POST answer from 200 to 405, so those differences are declared in the Safe change tests T11, T12 and T17.”
"Would require reliable database reads" removed; "Once the shared database interfaces..." becomes "neither depends on database changes (ForecastCache.java:107-116, StationCache.java:61-69)".
Now: "On the default run, ingest and the forecast finish before the API starts (run.sh:19-26), but in production the ingestion process may write ... while the Java API reads from it."
Now "API users (no in-repo client calls /api/v2)", and Risk 3 says "API users".
Finished and made team-wide (see the assembler section).
Was: “Entering quotation marks or other SQL-special characters in the station parameter.”
Now: “Sending a raw (unencoded) quote and the injection X'OR'1'='1 in the station parameter, which today return a 500 and every station's data respectively.”
"First, ... Second, ... Third, ..." with the missing space after "tests." fixed.
DbClient.java:57-68 becomes :50-74.
Part 3, Safe change
"Five fields" corrected to four things, and "clock and counter fields" to "clock fields".
A balanced injection returns every station's data; an unmatched quote gives 500.
120 ms then 240 ms, on the two measured routes.
Only the four routes that check the method answer POST with 405.
Now "the service's own code issues no writes", noting sqlite3 opens the store read-write.
T20 added.
T28 setup spelled out; Table 2 header and stations row fixed.
"43 source files"; "the 13 recorded paths".
T17 now says OPTIONS answers 204 and POST becomes 405 only with the newer handlers' GET check; the completeness note now says keep T26 until MRD-77 lands.