Skip to content

Commit e120094

Browse files
committed
Better fix for re-running scores after tester change on active challenge
1 parent ade44d1 commit e120094

8 files changed

Lines changed: 365 additions & 78 deletions

docs/Marathon Match Setup and Management.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Before starting:
2727
6. Save the scorer config separately with `Save Scorer Config`.
2828
7. Launch the challenge after the scorer config is saved and operationally ready.
2929
8. Monitor submissions from the Work app Submissions tab, including score links, artifacts, and ECS runner logs.
30-
9. If the tester changes during the challenge, publish a new version, update the scorer config, and rerun the latest submissions.
30+
9. If the tester changes during the challenge, publish a new version and update the scorer config; active configs automatically rerun the latest submissions for the open scoring phase.
3131

3232
## Challenge Creation
3333

@@ -284,8 +284,8 @@ Use this flow when the tester source changes after the challenge is active.
284284
2. In the scorer section, select the current scorer and click `New Version`.
285285
3. Create the new version with the updated Java source.
286286
4. Wait until compilation reaches `SUCCESS`.
287-
5. Save the scorer config so the challenge points at the new tester ID.
288-
6. Trigger a rerun of latest submissions for the open Submission/Provisional phase using the Work app rerun control or `POST /v6/marathon-match/challenge/:challengeId/rerun`.
287+
5. Save the scorer config so the challenge points at the new tester ID. If the config is active, this automatically reruns the latest submission from each submitter for the open scoring phase.
288+
6. Use the Work app rerun control or `POST /v6/marathon-match/challenge/:challengeId/rerun` only when you need to manually repeat the same latest-submission rescore.
289289
7. If Review/System scores also need to be regenerated, call `POST /v6/marathon-match/challenge/:challengeId/rerun/system`.
290290
8. Monitor the rerun ECS tasks, runner logs, artifacts, and review scores.
291291

@@ -296,12 +296,12 @@ Submission/Provisional rerun behavior:
296296
- Requires at least one open challenge phase.
297297
- Requires the scorer config to be active.
298298
- Requires the selected tester to be compiled successfully.
299-
- Requires a phase config that matches the currently open challenge phase.
299+
- Requires at least one phase config that matches the currently open challenge phase.
300300
- Fetches `isLatest=true` submissions from Submission API.
301-
- Queues one ECS task for the latest submission per member.
302-
- Uses the current open phase's scorer seeds and test count. During Submission this is normally the `PROVISIONAL` config.
301+
- Queues one ECS task for the latest submission per member per matching phase config.
302+
- Uses each matching phase config's scorer seeds and test count. During Submission this normally includes both `EXAMPLE` and `PROVISIONAL` when both are mapped to the Submission phase.
303303

304-
The response includes the queued submission IDs and ECS task IDs or per-submission errors.
304+
The response includes the queued submission IDs, phase config type, and ECS task IDs or per-submission errors.
305305

306306
System rerun behavior:
307307

@@ -399,7 +399,7 @@ During the challenge:
399399
- Watch Submission tab scores and timestamps.
400400
- Spot-check artifacts for early submissions.
401401
- Open runner logs for failures, timeouts, compile errors, and missing output.
402-
- If tester source changes, publish a new version and rerun latest submissions.
402+
- If tester source changes, publish a new version and update the scorer config. Active configs automatically rerun latest submissions; use the manual rerun control if you need to repeat that rescore.
403403

404404
After submission closes:
405405

docs/marathon-match-setup.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,9 @@ To switch an active challenge to a newer tester:
158158
1. Publish the new tester version with `PUT /v6/marathon-match/testers/:id`
159159
2. Wait for `GET /v6/marathon-match/testers/:newTesterId` to return `compilationStatus = SUCCESS`
160160
3. Update the challenge config with `PUT /v6/marathon-match/challenge/:challengeId` and the new `testerId`
161-
4. Trigger a rescore of current competitors with `POST /v6/marathon-match/challenge/:challengeId/rerun`
161+
4. The update automatically reruns the latest submission from each submitter when the config is active. You can also trigger the same rescore manually with `POST /v6/marathon-match/challenge/:challengeId/rerun`.
162162

163-
The standard rerun endpoint selects `isLatest` submissions for the challenge in received order and launches ECS scorer tasks for them in parallel using the scorer config for the currently open challenge phase. This does not happen automatically when `testerId` changes, so the rerun call is the operational step that recalculates Submission/Provisional scores.
163+
The standard rerun endpoint selects `isLatest` submissions for the challenge in received order and launches ECS scorer tasks for every scorer config that matches the currently open challenge phase. During Submission, this normally reruns both `EXAMPLE` and `PROVISIONAL` scoring when both configs are mapped to the Submission phase, so the latest displayed scores are recalculated with the current tester.
164164

165165
To restart Review/System tests, call:
166166

docs/marathon-processor-specification-and-scoring-terminology.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ Supported source extensions are:
2626

2727
| Extension | Language / runtime |
2828
| ----------- | ---------------------------------------------------------------------------- |
29-
| `.cpp` | C++ |
30-
| `.java` | Java |
29+
| `.cpp` | C++23 / GNU++23 using G++ 14.2.0 |
30+
| `.java` | Java 11 using Temurin OpenJDK 11.0.31+11 and `javac --release 11` |
3131
| `.py` | Python 3.12 |
3232
| `.cs` | C# using Mono |
3333
| `.cs_net10` | C# using .NET 10 / C# 14 |
3434
| `.cs_net7` | C# using .NET 7 / C# 11 |
35-
| `.rs` | Rust latest stable |
35+
| `.rs` | Rust 2024 edition using `rustc 1.96.0` stable as last verified |
3636

3737
The runner normalizes the selected source file into a temporary compile workspace before building or executing it.
3838

3939
### C++
4040

41-
C++ submissions are compiled with `g++` using GNU++23 and a fixed
41+
C++ submissions are compiled with G++ 14.2.0 using GNU++23 and a fixed
4242
`x86-64` architecture target with generic tuning. The runner does not use
4343
`-march=native`, so binaries do not depend on the specific Fargate host that
4444
compiled the submission.
@@ -50,7 +50,8 @@ g++ -std=gnu++23 -O3 -march=x86-64 -mtune=generic Solution.cpp -o Solution
5050

5151
### Java
5252

53-
Java submissions are compiled and executed with:
53+
Java submissions are compiled with Temurin OpenJDK `javac 11.0.31` and
54+
executed on the Java 11 runtime:
5455

5556
```bash
5657
javac --release 11 Solution.java
@@ -74,7 +75,8 @@ python3 Solution.py
7475

7576
### Rust
7677

77-
Rust submissions use the `.rs` extension and are compiled as a single source file with the latest stable Rust compiler:
78+
Rust submissions use the `.rs` extension and are compiled as a single source
79+
file with `rustc 1.96.0` stable as last verified:
7880

7981
```bash
8082
rustc --edition=2024 -O Solution.rs -o Solution

docs/submission-phase-scoring.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ Use:
120120

121121
`POST /v6/marathon-match/challenge/:challengeId/rerun`
122122

123-
This endpoint selects the latest submission for each member in received order and launches ECS scorer tasks in parallel using the scorer config that matches the currently open challenge phase. During Submission this is normally the challenge's `PROVISIONAL` phase config. Use it after changing the tester or when current latest submissions need to be rescored without waiting for new submission events.
123+
This endpoint selects the latest submission for each member in received order and launches ECS scorer tasks in parallel for every scorer config that matches the currently open challenge phase. During Submission this normally includes both `EXAMPLE` and `PROVISIONAL` when they are mapped to the Submission phase. It is also triggered automatically after an active challenge's `testerId` changes, and can be called manually when current latest submissions need to be rescored without waiting for new submission events.
124124

125125
For Review/System scoring reruns, use:
126126

src/api/marathon-match-config/marathon-match-config.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export class MarathonMatchConfigController {
142142
@ApiResponse({
143143
status: 400,
144144
description:
145-
'Challenge/config inactive, challenge has no open phase, tester is not compiled successfully, or PROVISIONAL phase config is missing.',
145+
'Challenge/config inactive, challenge has no open phase, tester is not compiled successfully, or no phase config matches the open phase.',
146146
})
147147
@ApiResponse({ status: 403, description: 'Forbidden.' })
148148
@ApiResponse({ status: 404, description: 'Marathon match config not found.' })

src/api/marathon-match-config/marathon-match-config.service.spec.ts

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ describe('MarathonMatchConfigService', () => {
254254
results: [
255255
{
256256
submissionId: 'submission-1',
257+
configType: PhaseConfigType.PROVISIONAL,
257258
taskId: 'task-1',
258259
},
259260
],
@@ -430,6 +431,182 @@ describe('MarathonMatchConfigService', () => {
430431
);
431432
});
432433

434+
it('reruns all phase configs mapped to the currently open submission phase', async () => {
435+
const { service, ecsService, httpService, m2mService, prisma } =
436+
createService();
437+
const user = {
438+
isMachine: false,
439+
userId: '40051399',
440+
} as never;
441+
442+
prisma.marathonMatchConfig.findUnique.mockResolvedValue({
443+
id: 'config-1',
444+
challengeId: '30000123',
445+
active: true,
446+
submissionApiUrl: 'https://submissions.example.com/v6',
447+
reviewScorecardId: 'scorecard-1',
448+
testerId: 'tester-1',
449+
testTimeout: 90000,
450+
compileTimeout: 120000,
451+
taskDefinitionName: 'mm-runner',
452+
taskDefinitionVersion: '7',
453+
tester: {
454+
compilationStatus: CompilationStatus.SUCCESS,
455+
},
456+
phaseConfigs: [
457+
{
458+
id: 'phase-provisional',
459+
configType: PhaseConfigType.PROVISIONAL,
460+
phaseId: 'submission-phase',
461+
startSeed: BigInt(100),
462+
numberOfTests: 50,
463+
},
464+
{
465+
id: 'phase-example',
466+
configType: PhaseConfigType.EXAMPLE,
467+
phaseId: 'submission-phase',
468+
startSeed: BigInt(1),
469+
numberOfTests: 10,
470+
},
471+
{
472+
id: 'phase-system',
473+
configType: PhaseConfigType.SYSTEM,
474+
phaseId: 'system-phase',
475+
startSeed: BigInt(1000),
476+
numberOfTests: 5000,
477+
},
478+
],
479+
});
480+
m2mService.getM2MToken.mockResolvedValue('m2m-token');
481+
httpService.get
482+
.mockReturnValueOnce(
483+
of({
484+
data: {
485+
status: 'ACTIVE',
486+
phases: [
487+
{
488+
phaseId: 'registration-phase',
489+
isOpen: true,
490+
actualStartDate: '2026-06-01T00:00:00.000Z',
491+
},
492+
{
493+
phaseId: 'submission-phase',
494+
isOpen: true,
495+
actualStartDate: '2026-06-01T00:00:00.000Z',
496+
},
497+
],
498+
},
499+
}),
500+
)
501+
.mockReturnValueOnce(
502+
of({
503+
data: {
504+
result: {
505+
content: [
506+
{
507+
id: 'submission-1',
508+
memberId: '40051399',
509+
submittedDate: '2026-06-02T00:00:00.000Z',
510+
isLatest: true,
511+
virusScan: true,
512+
},
513+
{
514+
id: 'submission-2',
515+
memberId: '40051400',
516+
submittedDate: '2026-06-03T00:00:00.000Z',
517+
isLatest: true,
518+
virusScan: true,
519+
},
520+
],
521+
},
522+
},
523+
headers: {
524+
'x-total-pages': '1',
525+
},
526+
}),
527+
);
528+
ecsService.launchScorerTask.mockImplementation((...args: unknown[]) => {
529+
const submissionId = String(args[1]);
530+
const scoringPhase = args[3] as { configType: PhaseConfigType };
531+
return Promise.resolve({
532+
taskArn: `arn:aws:ecs:task/${scoringPhase.configType}-${submissionId}`,
533+
taskId: `${scoringPhase.configType}-${submissionId}`,
534+
});
535+
});
536+
537+
const result = await service.rerunLatestSubmissions('30000123', user);
538+
539+
expect(result).toEqual({
540+
challengeId: '30000123',
541+
submissionsQueued: 2,
542+
results: [
543+
{
544+
submissionId: 'submission-1',
545+
configType: PhaseConfigType.EXAMPLE,
546+
taskArn: 'arn:aws:ecs:task/EXAMPLE-submission-1',
547+
taskId: 'EXAMPLE-submission-1',
548+
},
549+
{
550+
submissionId: 'submission-2',
551+
configType: PhaseConfigType.EXAMPLE,
552+
taskArn: 'arn:aws:ecs:task/EXAMPLE-submission-2',
553+
taskId: 'EXAMPLE-submission-2',
554+
},
555+
{
556+
submissionId: 'submission-1',
557+
configType: PhaseConfigType.PROVISIONAL,
558+
taskArn: 'arn:aws:ecs:task/PROVISIONAL-submission-1',
559+
taskId: 'PROVISIONAL-submission-1',
560+
},
561+
{
562+
submissionId: 'submission-2',
563+
configType: PhaseConfigType.PROVISIONAL,
564+
taskArn: 'arn:aws:ecs:task/PROVISIONAL-submission-2',
565+
taskId: 'PROVISIONAL-submission-2',
566+
},
567+
],
568+
});
569+
expect(ecsService.launchScorerTask).toHaveBeenCalledTimes(4);
570+
expect(ecsService.launchScorerTask).toHaveBeenNthCalledWith(
571+
1,
572+
'30000123',
573+
'submission-1',
574+
{
575+
taskDefinitionName: 'mm-runner',
576+
taskDefinitionVersion: '7',
577+
},
578+
{
579+
configType: PhaseConfigType.EXAMPLE,
580+
startSeed: BigInt(1),
581+
numberOfTests: 10,
582+
scorecardId: 'scorecard-1',
583+
},
584+
undefined,
585+
{
586+
memberId: '40051399',
587+
},
588+
);
589+
expect(ecsService.launchScorerTask).toHaveBeenNthCalledWith(
590+
3,
591+
'30000123',
592+
'submission-1',
593+
{
594+
taskDefinitionName: 'mm-runner',
595+
taskDefinitionVersion: '7',
596+
},
597+
{
598+
configType: PhaseConfigType.PROVISIONAL,
599+
startSeed: BigInt(100),
600+
numberOfTests: 50,
601+
scorecardId: 'scorecard-1',
602+
},
603+
undefined,
604+
{
605+
memberId: '40051399',
606+
},
607+
);
608+
});
609+
433610
it('marks latest submissions failed without launching when virus scan has not passed', async () => {
434611
const {
435612
service,
@@ -530,6 +707,7 @@ describe('MarathonMatchConfigService', () => {
530707
results: [
531708
{
532709
submissionId: 'submission-1',
710+
configType: PhaseConfigType.PROVISIONAL,
533711
error:
534712
'Marathon Match PROVISIONAL scoring skipped because the submission has not passed virus scanning.',
535713
},
@@ -878,6 +1056,7 @@ describe('MarathonMatchConfigService', () => {
8781056
submissionsQueued: 10,
8791057
results: submissionIds.map((submissionId) => ({
8801058
submissionId,
1059+
configType: PhaseConfigType.PROVISIONAL,
8811060
taskArn: `arn:aws:ecs:us-east-1:123456789012:task/${submissionId}`,
8821061
taskId: `task-${submissionId}`,
8831062
})),

0 commit comments

Comments
 (0)