Dependency: READY — start now. No open blockers. Blocks cs3600_2026#42 (the T2 bundle loader can't be built until this serves bots). Reuses existing storage/HMAC/auth machinery; the new parts are the artifact entity, team-bound links, and the fetch-budget ledger.
Tier 2 — streamed TA-bot delivery
The delivery half of the three-tier architecture (OBFUSCATION_PITCH.md §5 B / §6 Increment 1b). An authenticated, rate-limited endpoint that serves a pre-built compiled TA-bot to a student per session, team-bound and metered, so practice runs on the student's machine (zero server compute) without leaving a permanent copy on disk.
Framing — read before building: this tier is deterrence + toil reduction + metered acquisition, not protection. Code that runs in a process the student controls is recoverable regardless of how it was delivered (debugger, or an AI behavioural-cloning loop against PlayerAgent.play). The protection guarantee comes only from the T3 server-side tier (#160). Do not describe this endpoint as protecting strategy.
Build (nothing here exists to reuse as-is)
- TA-bot artifact entity + migration — competition-scoped, admin-published, versioned, owning a
FileRecord by FK. Model it on GameMatchFile (that is where visibility/authz live; FileRecord is a dumb blob row). No artifact/release concept exists in the codebase today.
- Team-bound signed links.
HmacService.sign currently signs only "GET\n"+uuid+"\n"+exp — a transferable bearer token — and /files/** is permitAll with no download accounting. Extend the signed payload with the team UUID and serve T2 artifacts through a dedicated authenticated route that can count downloads, rather than the anonymous /files/**.
- Per-team-per-window fetch budget. No such table or pattern exists (the only limiter is the in-flight concurrency gauge in
PrivateGameMatchController). Use the same atomic upsert as the T3 scrim budget:
INSERT INTO ta_bot_fetch_usage (team_id, artifact, window_start, count) VALUES (…, 1)
ON CONFLICT (team_id, artifact, window_start)
DO UPDATE SET count = ta_bot_fetch_usage.count + 1 WHERE ta_bot_fetch_usage.count < :cap
RETURNING count;
Return Retry-After on rejection; the row doubles as the attribution ledger (§8) and telemetry.
- Config for which bots are T2-eligible per competition —
Competition.settings jsonb or an entity flag.
Reuse
LocalStorageService.store(compress=true, maxBytes) + getDownloadLink; StorageController byte-serving (note: no HTTP Range support today — for multi-MB over flaky networks consider UrlResource via loadAsResource); @AuthenticationPrincipal User + playerService.getPlayer + teamService.isMember + 403/404 idiom.
No turnstile
Consistent with #157 removing it from the sibling match endpoint; the fetch budget is the abuse control.
CI feed
CI already builds per-platform artifacts (~0.3–1 MB each). Start with manual admin upload into the artifact entity (same flow as submissions); automate later.
Blocks the bundle-side loader (cs3600_2026). Depends on nothing.
Tier 2 — streamed TA-bot delivery
The delivery half of the three-tier architecture (
OBFUSCATION_PITCH.md§5 B / §6 Increment 1b). An authenticated, rate-limited endpoint that serves a pre-built compiled TA-bot to a student per session, team-bound and metered, so practice runs on the student's machine (zero server compute) without leaving a permanent copy on disk.Framing — read before building: this tier is deterrence + toil reduction + metered acquisition, not protection. Code that runs in a process the student controls is recoverable regardless of how it was delivered (debugger, or an AI behavioural-cloning loop against
PlayerAgent.play). The protection guarantee comes only from the T3 server-side tier (#160). Do not describe this endpoint as protecting strategy.Build (nothing here exists to reuse as-is)
FileRecordby FK. Model it onGameMatchFile(that is where visibility/authz live;FileRecordis a dumb blob row). No artifact/release concept exists in the codebase today.HmacService.signcurrently signs only"GET\n"+uuid+"\n"+exp— a transferable bearer token — and/files/**ispermitAllwith no download accounting. Extend the signed payload with the team UUID and serve T2 artifacts through a dedicated authenticated route that can count downloads, rather than the anonymous/files/**.PrivateGameMatchController). Use the same atomic upsert as the T3 scrim budget:Retry-Afteron rejection; the row doubles as the attribution ledger (§8) and telemetry.Competition.settingsjsonb or an entity flag.Reuse
LocalStorageService.store(compress=true, maxBytes)+getDownloadLink;StorageControllerbyte-serving (note: no HTTP Range support today — for multi-MB over flaky networks considerUrlResourcevialoadAsResource);@AuthenticationPrincipal User+playerService.getPlayer+teamService.isMember+ 403/404 idiom.No turnstile
Consistent with #157 removing it from the sibling match endpoint; the fetch budget is the abuse control.
CI feed
CI already builds per-platform artifacts (~0.3–1 MB each). Start with manual admin upload into the artifact entity (same flow as submissions); automate later.
Blocks the bundle-side loader (
cs3600_2026). Depends on nothing.