Skip to content

Add server-selected strict interview mode - #285

Merged
jserv merged 1 commit into
sysprog21:mainfrom
ZuoTso:fix-interview-refresh
Oct 23, 2026
Merged

jserv merged 1 commit into
sysprog21:mainfrom
ZuoTso:fix-interview-refresh

Conversation

@ZuoTso

@ZuoTso ZuoTso commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Introduce an opt-in Strict Interview mode for more honest self-assessment, reducing the ability to repeatedly practice a familiar problem through URL selection or page refresh.

Strict assigns problems on the server based on the selected difficulty, while Practice remains unchanged. A normal same-tab refresh starts a new attempt and excludes the previous assignment when its receipt is available. Session recovery is not included.

Testing

./scripts/test.sh passed (1,141 tests, no failures or skips; 49 hook checks). Manually verified with make web.

Closes #185


Summary by cubic

Adds an opt-in Strict Interview mode that assigns a matching problem server-side after media preflight, closing #185 by preventing repeated practice of a known problem via URL or refresh. Practice mode keeps its current behavior; Strict requires GitHub sign-in.

Selection and refresh behavior

  • Difficulty remains a multi-select choice; the server draws from matching problems, excluding the previous assignment.
  • A signed receipt in session storage remembers the previous problem per tab; clearing or replaying it resets the exclusion, and storage failure does not start a new sequence.
  • Reloading returns to preflight, then starts a fresh attempt with a different problem; admission is refused with HTTP 409 if none remains.
  • Strict ignores URL problem selection, does not fall back to offline practice, and restores no interview progress.
  • Strict always starts a fresh room, bypassing a pinned development room.

Written for commit 5c53026. Summary will update on new commits.

View guided diff

cubic-dev-ai[bot]

This comment was marked as resolved.

@ColtenOuO ColtenOuO left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squash the commit and remove Co-authored-by: xxx in commit message.

Addressing review feedback doesn't require adding a new commit message.

@ZuoTso
ZuoTso force-pushed the fix-interview-refresh branch from 8b0e3df to 95a5fd3 Compare October 11, 2026 21:31
@ZuoTso

ZuoTso commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Squash the commit and remove Co-authored-by: xxx in commit message.

Addressing review feedback doesn't require adding a new commit message.

Squashed into "95a5fd3" and removed the "Co-authored-by" trailer.

Comment thread src/web/selection.rs
let choices: Vec<_> = problems
.iter()
.filter(|problem| difficulties.contains(&problem.difficulty))
.filter(|problem| Some(problem.variant().page) != previous)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excluding only previous means the pool never shrinks: each reload is a fresh uniform draw from every other problem at those levels. A candidate who already knows some of them can keep reloading until one of those comes up, which is the "practice a familiar problem" case #185 is about. Each reload also sends out a new agent while the old room keeps its capacity slot. If that is acceptable for a self-assessment mode, say so in the limits paragraph of docs/interview-selection.md. Otherwise, a follow-up could store the draws in a bounded, signed set (or keep them on the server per account) so that reloading moves through the pool instead of re-rolling.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an intentional limitation of the opt-in self-assessment mode. Excluding only the previous problem reduces immediate repetition but does not prevent deliberate re-rolling. Strict retains the existing agent cleanup policy, accepting temporary capacity overlap. Both trade-offs are now documented in docs/interview-selection.md.

@jserv

jserv commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

Squashed into "95a5fd3" and removed the "Co-authored-by" trailer.

You don't have to make statements like this, as GitHub always tracks commits.

jserv

This comment was marked as outdated.

@ZuoTso
ZuoTso force-pushed the fix-interview-refresh branch from 6e9c9b4 to afd7a92 Compare October 13, 2026 16:28
Comment thread web/app.js
@ZuoTso
ZuoTso force-pushed the fix-interview-refresh branch 2 times, most recently from 2a49315 to e03a0e2 Compare October 14, 2026 09:18
@jserv

This comment has been minimized.

1 similar comment
@jserv

This comment was marked as outdated.

@ZuoTso
ZuoTso force-pushed the fix-interview-refresh branch from e03a0e2 to 08a21b1 Compare October 19, 2026 05:48
jserv

This comment was marked as outdated.

@ZuoTso
ZuoTso force-pushed the fix-interview-refresh branch from 08a21b1 to 29c232f Compare October 19, 2026 09:49
@jserv

This comment has been minimized.

1 similar comment
@jserv

This comment has been minimized.

@ZuoTso
ZuoTso force-pushed the fix-interview-refresh branch from 29c232f to abc6eef Compare October 20, 2026 04:16
Comment thread web/interview.js Outdated
@jserv

This comment has been minimized.

Comment thread src/web/token.rs Outdated
Comment thread web/interview.js Outdated
jserv

This comment was marked as outdated.

Comment thread web/interview.js Outdated
@jserv

jserv commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

#440 adds a 10 s deadline on /api/token (TOKEN_REQUEST_TIMEOUT_MS) and rewrites the resulting TimeoutError to "The interview connection timed out. Offline practice is ready." Your Strict branch in the same catch shows error.message while refusing offline practice, so once both land, a Strict start that times out would say offline practice is ready. Whichever of the two lands second needs the timeout to reach the Strict branch with wording of its own; please plan for that when rebasing.

@ZuoTso
ZuoTso force-pushed the fix-interview-refresh branch from abc6eef to cdcc80e Compare October 23, 2026 10:10
Comment thread web/interview.js Outdated
Comment thread web/interview.js
Comment thread web/interview.js Outdated

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase latest main branch and resolve conflicts.

@ZuoTso
ZuoTso force-pushed the fix-interview-refresh branch from cdcc80e to 3624b7c Compare October 23, 2026 16:11
Comment thread web/interview.js
await connectLiveKit(connection, preflight, presenting);
state.connected = true;
if (strict) updateRunAvailability();
if (strict && !whiteboard) nodes.editor.disabled = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This turns the editor back on without checking anything else. A pause or a behavioral round_state can arrive while connectLiveKit is still running, and this line then makes the editor editable when it should stay locked. Suggest nodes.editor.disabled = state.paused || codingClosed();, like the other places that turn the editor back on.

Comment thread web/interview.js
"connection",
providerUiState("degraded", "The interview connection dropped.").message,
strict
? "The Strict Interview connection dropped."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the start fails after the room was joined, the catch block in connect calls joined.disconnect(). That fires this handler later, which replaces the specific failure banner (the timeout or Egress refusal) with this generic one. The catch has already cleared state.room, so setting the banner only when state.room === room would keep the real reason on screen.

Comment thread web/app.js
// waits for a signed-in account and suggested difficulties to settle.
start.disabled =
(selectionMode === "practice" && !problem) ||
(selectionMode === "strict" &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a deployment that requires login, a signed-out candidate who picks Strict sees Start labeled "Use GitHub to start", but this condition keeps it disabled (accountHistory !== true). So the sign-in-on-Start path through signInFirst is never reached, and clicking does nothing. Either let Strict through when signInFirst is set, or change the label for Strict.

Practice needs reproducible problem links, while strict
interviews should prevent candidates from choosing known
problems. Preserve difficulty selection in both modes and
start a new strict attempt on reload instead of recovering
the previous interview.
@ZuoTso
ZuoTso force-pushed the fix-interview-refresh branch from 3624b7c to 5c53026 Compare October 23, 2026 17:12

@ColtenOuO ColtenOuO left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, very nice PR :) Thanks for your contribution and patience.

@ColtenOuO
ColtenOuO requested a review from jserv October 23, 2026 18:13
@jserv
jserv merged commit 85135ba into sysprog21:main Oct 23, 2026
13 checks passed
@jserv

jserv commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Thank @ZuoTso for contributing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refreshing or reusing an interview URL allows restarting a known problem

3 participants