Skip to content

Never tie scores: reachable 5,000 rubric, score_taken, board-first serving - #29

Open
arilivigni wants to merge 1 commit into
mainfrom
arilivigni-no-tie-scoring
Open

arilivigni wants to merge 1 commit into
mainfrom
arilivigni-no-tie-scoring

Conversation

@arilivigni

Copy link
Copy Markdown
Contributor

Why

Scores tied: every category paid in steps of 50, and the real ceiling was 4,600 even though MAX_SCORE is 5,000. With this change, no two active drinks share a score, and 5,000 is reachable.

What changes

Rubric (services/name-score.mjs). Still a pure function of the name.

  • The categories are re-weighted, and a new letter value category is added (A=1 to Z=26, summed and capped at 400).
  • The category maxima sum to 5,000. A test shows one valid name, "Monaduckycopilot Moka Mocha Mint Squint", earning every category maximum and scoring exactly 5,000.
  • Collisions are still possible in a finite range. They are handled by refusing admission, not promised away.

Leaderboard service (store.mjs, app.mjs)

  • A different drink whose score an active entry already holds gets 409 score_taken.
  • The check runs inside admit under the existing write lock, so concurrent requests cannot both land.
  • A resend of the same drink is still a success, and a takedown frees the score.
  • Ledger, reservation, moderation, persistence and the captured tally are unchanged.

Booth (booth-engine.mjs)

  • With leaderboardApi, the flow is score → admit → serve. The attempt and its publication token are written to the ledger (run.admission) before sending, and the drink is served only after the board admits it.
  • Refusal (score_taken, duplicate_drink, handle_taken, unavailable_drink): the attendee stays on the naming screen. score_taken asks them to tweak the name.
  • In doubt (no answer, 5xx, board_rebuilding, a mismatched receipt, or any other 4xx on a resend): nothing is served and the booth returns 503 leaderboard_unreachable ("try again in a moment"). The next try, the retry sweep, or a wipe resends the same token, so the drink is served and admitted exactly once.
  • Earlier attempt still in doubt: it is settled first. If the board took it, that becomes the attendee's drink.
  • 409 admission_in_progress stops a double tap or a second process.
  • archiveAndWipe refuses with admissions_unsettled until every admission is settled.
  • Standalone booths (no leaderboardApi) refuse a score already on the local menu with the same score_taken.
  • Surfaces: the panel's background publish is removed. The canvas shows a "Checking your drink…" status. The canvas description and the order-up skill are updated.

Docs: integration contract, leaderboard service, architecture, RUNBOOK, CHEAT-SHEET, README, step 1, and SKILL.md.

Tests

npm test: 353/353 pass, and npm run check is clean. New tests/board-first.test.mjs runs against the real service and covers:

  • a cross-booth score_taken, then a tweak that lands;
  • two booths racing for one score, where exactly one is served;
  • a takedown freeing the score;
  • a lost reply, served once with the same token and counted once on the board;
  • an earlier in-doubt name that becomes the drink, or is refused and frees the new name;
  • board_rebuilding;
  • a wipe blocked while an admission is in doubt;
  • a proxy 4xx on a resend, which stays in doubt, so there is no second admission;
  • a standalone booth refusing a duplicate score.

The service, sync, engine, menu and name-score suites are updated as well.

Reviewer check: renderer/booth.html/booth.js gain a hidden #name-checking notice. Per docs/architecture.md, drive the booth screen in a real canvas before the event.

⚠️ Rollout: coordinated, do not deploy yet

The service re-scores every submission, so a scoring mismatch gets 422 score_mismatch. Old booth builds also serve a drink before publishing it. The service and every booth must therefore run the exact same merge commit.

  1. Blocker: wait for session c347c7b1 (PR Record the 2026-10-29 leaderboard deployment in the runbook #28) to confirm the production board is backed up and cleared. Stored entries are never re-scored, so old scores would otherwise remain.
  2. Keep every booth closed, or with leaderboardApi unset, during the change.
  3. Deploy the service from the merge commit (npm run leaderboard:package plus the zip deploy). Verify the build by diffing /home/data/SitePackages/<packagename.txt> from Kudu against the merge commit.
  4. Update every booth machine to the same commit and run npm test there.
  5. Reopen the booths. Never run an older booth build against this service.

Do not merge until reviewed. Auto-merge is not enabled.

…rving

- Rubric: finer categories plus a letter-value category; maxima sum to 5,000
  and a test shows one valid name scoring exactly 5,000.
- Service: refuse a different drink whose score an active drink holds
  (409 score_taken) inside the admission under the write lock; resends stay
  idempotent and takedowns free the score.
- Booth: with a leaderboard, score -> admit -> serve. Refusals keep the
  attendee naming; no answer keeps the attempt in the ledger and resends the
  same token, so a drink is served and admitted once. Wipes refuse while an
  admission is unsettled. Standalone booths enforce unique scores locally.
- Surfaces, docs, and tests updated.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 18:05

Copilot AI 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.

🟡 Changes recommended

Unresolved publication attempts can still settle after rejection or restart, allowing duplicate drinks for the same attendee and potentially overwriting their submission.

3 open issues
What changed in this PR

This PR makes 5,000 points attainable and prevents new active drinks from sharing a score. Configured booths now wait for leaderboard admission before serving, with persisted attempts for retry recovery.

Changes:

  • Rebalance the rubric and add single-point letter values.
  • Enforce score uniqueness and introduce board-first serving.
  • Update attendee feedback, operational guidance, and regression coverage.
File Description
tests/​name-score.test.mjs Verify reachable 5,000 and single-point scoring.
tests/​leaderboard-sync.test.mjs Cover admission failures and retry recovery.
tests/​leaderboard-service.test.mjs Test score conflicts, races, and revised publication flow.
tests/​booth-menu.test.mjs Cover standalone score uniqueness and preparation.
tests/​booth-engine.test.mjs Give multi-drink fixtures distinct scores.
tests/​board-first.test.mjs Add end-to-end admission and recovery coverage.
README.md Summarize scoring and admission guarantees.
leaderboard-service/​store.mjs Reject occupied scores during atomic admission.
leaderboard-service/​app.mjs Return 409 score_taken for score conflicts.
docs/​leaderboard-service.md Document admission behavior and coordinated deployment.
docs/​integration-contract.md Define score uniqueness and board-first contracts.
docs/​architecture.md Explain persisted admissions and regression requirements.
booth/​RUNBOOK.md Update outage handling and staff procedures.
booth/​CHEAT-SHEET.md Add quick guidance for outages and score conflicts.
.github/​steps/​1-name-a-drink.md Explain score tweaks and the revised breakdown.
.github/​skills/​order-up/​SKILL.md Update attendee guidance for admission outcomes.
.github/​extensions/​commit-and-sip/​services/​name-score.mjs Reweight categories and add letter values.
.github/​extensions/​commit-and-sip/​services/​leaderboard.mjs Classify score conflicts as terminal publication refusals.
.github/​extensions/​commit-and-sip/​services/​booth-menu.mjs Separate preparation from insertion; optionally enforce unique scores.
.github/​extensions/​commit-and-sip/​renderer/​booth.js Show checking status during submission.
.github/​extensions/​commit-and-sip/​renderer/​booth.html Add an accessible checking notice.
.github/​extensions/​commit-and-sip/​canvas.mjs Describe board-first submission outcomes.
.github/​extensions/​commit-and-sip/​booth-panel.mjs Remove post-serving background publication.
.github/​extensions/​commit-and-sip/​booth-engine.mjs Persist and settle admissions before serving; guard event wipes.

Review effort: Balanced

// A standalone booth is its own board, so it refuses a score another
// drink on its menu already holds. A booth with an event leaderboard
// never reaches here: submitName asks the board instead.
const entry = addDrink(this.houseMenu(data), {
Comment on lines +999 to +1001
if (refusedByBoard(outcome.error, firstSend)) {
const refusal = admissionRefusal(outcome.error.code, admission.entry);
delete run.admission;
this.publicationSweep ??= (async () => {
try {
if (!this.leaderboardClient) return 0;
await this.resolveAdmissions();
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.

2 participants