Feat/community - #10
Merged
Merged
Conversation
The candidate side interrogates a repo before it counts for anything. This does
the same to a job posting: the employer answers for the role they just wrote,
under the same clock and the same single adaptive follow-up, and the posting is
published only if the answers clear 70/100.
Same engine rather than a copy. The clock, the paste and timing ranking in
pick_suspect_answer, and the follow-up generator are all shared with the repo
quiz; only the prompts differ, plus a `framing` argument whose default keeps the
candidate wording byte-identical.
Two gaming holes closed by construction:
* The draft is held server-side from generation and the published posting is
built from that copy, so a company cannot defend an honest draft and publish
a rosier one.
* `status: "graded"` is terminal and carries the job id it produced, so a
replayed final call returns the stored outcome instead of minting a second
posting. Grading also refuses to run if the follow-up round was skipped.
Removes POST /jobs/, which created postings with no auth at all and would have
left the gate decorative. Nothing called it. GET /jobs/ and POST /jobs/apply are
unchanged.
Company attempts get their own collection rather than a discriminator on
quiz_attempts: has_graded_attempt_scoring_at_least() backs the candidate reveal
threshold and matches any graded attempt by user, so a shared collection would
let an employer quiz count as comprehension a candidate never demonstrated.
Frontend adds the employer-only Post a Job tab, reusing the quiz feature
QuestionCard so both sides of the market run the same clock and the same silent
paste recording.
38 new backend tests; suite is 151 green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDgAeL6TE9UCqkWUy6azim
The registration form already sent a role and register_user already stored it; what was wrong was the presentation and the nav around it. The role picker was a dropdown. It decides which half of the product you land in, so both options are now radios, visible without opening anything, each saying what picking it means. Navigation follows from it: an employer sees Post a Job and Profile, a candidate sees Quiz and Profile. The repo quiz is a candidate instrument and its score means nothing on the employer side, so offering it to an employer was offering a number that could not be read. Hiding a tab remains a convenience rather than the gate - every employer-only route re-checks the role against the signed token, and a candidate token still gets a 403. Adds the test that was missing between the two: registering as an employer must put "employer" in the issued token, not only in the stored document. The employer gate reads the token, so a role dropped between those two points would mean signing up as an employer and landing in an account that cannot reach the company quiz. Verified against the running stack with an account registered through the form: a defended posting scored 98 and published, a template posting answered in boilerplate scored 0 and published nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDgAeL6TE9UCqkWUy6azim
Combines the two things this platform can actually observe about a candidate: what they defended here, and what happened when they applied elsewhere. overall = comprehension * 0.75 + min(rounds / 4, 1) * 100 * 0.25 Comprehension carries most of the weight because it is the thing we verify; round history is corroboration from outside this system, which is worth something precisely because we did not generate it. Rounds saturate at four, so volume of applications cannot substitute for understanding - the raw count is still reported, just not rewarded past the cap. The components ship with the total rather than behind it, for the same reason ScoreResult never renders a bare quiz score. One blended figure gets read as a measure of engineering ability and it is not one: a 92 average across one quiz and a 92 across six are different claims, and only quiz_count says so. Ungraded attempts are excluded, matching the reveal threshold - an attempt awaiting its follow-up has no score anyone has stood behind. `applied` is the only application status that does not count as a round reached; `rejected` does, which is the arguable one. Status is a single field that moves, so a rejection after three rounds and a rejection at first screening are stored identically, and refusing to count it would erase the former to avoid crediting the latter. Deliberately NOT wired into meets_reveal_threshold(). The reveal is a one-way latch on someone's identity, and moving it onto a formula whose weights are still guesses would latch accounts open on a number nobody has calibrated. Both docs say so. The page is its own tab rather than part of the quiz result view: that view is about one attempt on one repo, this is about a history. Verified against live Mongo as well as the mocked unit tests, since the two new queries only actually run there: a fresh account reads zeros, and an account with two graded quizzes and one accepted application reads 80/2/1 for an overall of 66 - with an ungraded 100 and a still-applied application correctly ignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDgAeL6TE9UCqkWUy6azim
Two collections, two repositories, one service. Connections are instant and mutual; posts are created and listed. That is the whole feature. A connection is stored once per pair rather than twice - a sorted two-element `users` array, so a lookup finds it from either side and the same two people can only ever produce one document. Two directed rows would mean every read unions two queries and every write has to keep them in step. Connecting again is a no-op that reports created: false; with no approval flow there is no state for a repeat to advance, so erroring would only punish a double-click. Post authors come from the access token, never from the body, the same rule the quiz uses. Text is trimmed and capped, and a job reference is checked to exist before the post is written. The one part that is not lightweight is identity. Both surfaces list other people, which makes them the two places the anonymous funnel could quietly leak. Every name leaving the service goes through one `_display` helper that reads the latched `revealed` flag and drops the name when it is False, so an unrevealed candidate is a pseudonym in a feed and in a connections list and the real name is never in the payload at all. That needed a batch lookup on user_repository - a page of posts resolving one author per row is how a list view starts costing twenty round trips. This module deliberately does not evaluate the reveal threshold. That belongs to the profile read, where flipping the latch is documented behaviour. A candidate who has just earned their reveal therefore shows here as themselves only after their profile has been read, which is the safe direction to be wrong in. Out of scope on purpose, each one a schema change rather than a flag so the feed cannot drift into a social network by default: DMs, threaded replies and comments, likes and reactions, approval-required connections, media. Recorded in the README and in models/community.py; a test asserts no route mentions any of them. Verified against live Mongo as well as the mocked tests: a mutual connection reads as one from both sides, a repeat reports created: false, and both posts in the feed render as Anonymous Candidate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDgAeL6TE9UCqkWUy6azim
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.