Repository navigation
feat(api): accept a result proven by two check-ins instead of an operator - #327
Closed
patrick-hermann-sva wants to merge 4 commits into
Closed
patrick-hermann-sva wants to merge 4 commits into
patrick-hermann-sva wants to merge 4 commits into
Conversation
ADR-0024: a result from a match both players checked in to has no operator. entered_via gains 'checkin', reported_by may be null for exactly that value, and the match points at its two check-ins, each of which carries at most one result (partial unique indexes, plus CheckinRepository.Lock to serialise the read-then-write). PendingFor and PendingCountFor compare with "is distinct from": with "<>" a null reporter hid the match from both players, the only two people who can confirm it. Refs #320 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ator POST /api/results takes home_checkin_id and away_checkin_id in place of operator_id. internal/checkin verifies them: own player each, different halves, unused, at most three hours old. The match is stored pending with entered_via 'checkin' and no reporter, so either player confirms. A reused check-in is 409, so a retrying Zählwerk learns the first copy arrived. Sending both proofs, or one check-in, is 400. The pending list says the Zählwerk entered it and credits a loss to the opponent rather than to the reporter, which was also wrong for kiosk and scoreboard results, whose reporter is the operator. Refs #320, #323 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
definition-of-done.sql gets an "of which checkin" column; the verdict already counts only entered_via = 'player'. The unsettled-results metric exports the new origin, at zero too. admin-and-observers.md says what a null reported_by means. Refs #320, #323 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The players stood at the table and know they checked in; the line now has the same shape as "Bodo hat eingetragen". Refs #320 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Superseded by the PR opened from the same branch against |
This branch was successfully deployed
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.
Stacked on #326 (fob resolve, which creates the check-ins this cites). Once #326 is merged I rebase this onto
main; until then the diff againstmainshows both.Builds the operator-free half of #320 as ADR-0024 decides it. Auto-accept is not in here (see below).
What changes
POST /api/resultstakes two check-ins instead of an operator.{"home_id": "…", "away_id": "…", "home_checkin_id": "…", "away_checkin_id": "…", "sets": [[11, 9], [11, 7]], "best_of": 3, "points_to_win": 11}The ids are the
checkin_ids thatPOST /api/identities/rfid/resolvereturned. Accepted only if:operator_idalongsidecheckin.Validity= 3 hThe operator path is unchanged; without check-ins
operator_idis still required.The match is stored
pending,entered_via = 'checkin',reported_bynull,home_checkin_id/away_checkin_idset. Either player confirms (scoring.loadalready allows every participant who is not the reporter).Schema (
20261015090000, additive):entered_viaaccepts'checkin'reported_bydrops NOT NULL, withcheck (reported_by is not null or entered_via = 'checkin')checkins, with a CHECK that they are set exactly on checkin rows (each one separately)CheckinRepository.Lockreads both check-insfor update, in id order, and then asks in a separate statement whether a match cites them. A separate statement is needed because under read committed, a statement that waited for the lock still sees its old snapshot.One trap fixed on the way.
PendingFor/PendingCountForfiltered withreported_by <> $1. For a null reporter that is null, so the match would have reached neither player. It is nowis distinct from.Pending list. A match without a reporter reads „Das Zählwerk hat eingetragen: 2:1 für Anna“. The loss case now credits the opponent, not the reporter. That was also wrong for kiosk and scoreboard results, whose reporter is the operator („3:1 für “).
/adminlabels the origin „Zählwerk, angemeldet“.Measurement.
definition-of-done.sqlgets an "of which checkin" column. The verdict already counts onlyentered_via = 'player', so these rows stay out of it, as ADR-0024 asks.schmetterpause_matches_openexportsentered_via="checkin", zero included.Verified
go test -race ./...green, golangci-lint v2.14.0 0 issues,task ci(Dagger) green locally, including verify against the built image.postgres:17-alpine: round trip, both players see the match inPendingFor, the schema refuses a reporter-less player row, a checkin row without check-ins, one check-in on a player row, and check-ins on a scoreboard row;Lockmarks used ones; reuse →ErrConflict.<>restored,TestACheckedInMatchHasNoReporterAndCitesItsCheckinsfails (PendingFor(Anna) = 0). Withoutfor update,TestTwoResultsCannotShareACheckinfails: three of four concurrent transactions run into the index. The test had to be made to overlap (200 ms between read and write) before it could tell the two apart. Its first version passed either way.definition-of-done.sqlruns and shows the new column (empty database, so syntax and shape only).internal/server/checkin_results_test.go: two fobs linked and resolved at both halves, the result posted, both players' pending lists show it, Bea confirms, the match is confirmed.Not verified: nothing ran against a real Zählwerk. zaehlwerk#92 and zaehlwerk-firmware#63 are not built, so DoD point 3 of #323 (a checked-in match on the office's instance) is still open. The migration has not run against a copy of production data. It touches no rows, but the new CHECK constraints are validated over all existing matches, and every existing row has
entered_via≠checkinwith null check-in ids, which satisfies them.Left open, deliberately
Refs #320, notCloses.table_idyet, as in feat(fobs): link a key fob in a window and resolve it for the Zählwerk #326.Refs #320, #323, #326
🤖 Generated with Claude Code