Skip to content

Feat/community - #10

Merged
krishhimself merged 4 commits into
mainfrom
feat/community
Aug 24, 2026
Merged

Feat/community#10
krishhimself merged 4 commits into
mainfrom
feat/community

Conversation

@krishhimself

Copy link
Copy Markdown
Owner

No description provided.

krishhimself and others added 4 commits August 24, 2026 17:51
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
@krishhimself
krishhimself merged commit 89e62a3 into main Aug 24, 2026
2 checks passed
@krishhimself
krishhimself deleted the feat/community branch August 27, 2026 18:28
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.

1 participant