Repository navigation
ios: bind placement answers to the submitted scene - #198
Conversation
…s sent The placement client decoded the answer's stats.input_sha256 but never compared it with what it sent (#144). The server hashes the exact scene bytes it parses: for a bare scene.json that is the request body (server/api.py _parse, server/scene.py). The result schema requires the field as 64 lowercase hex digits. HTTPResultClient now runs ResultBinding.check on every 2xx answer before returning it. The check reads stats.input_sha256 with JSONDecoder, requires the schema's form, and compares it with the SHA-256 of the bytes sent. It fails closed: - an answer it can't read, or one without the field, is refused as noInputHash; - a value that isn't 64 lowercase hex digits is refused as malformedInputHash; - a hash for another scene is refused as mismatch. Each refusal reaches the existing "We couldn't read the server's answer" recovery and isn't retried. SampleResultClient answers no particular scene (its hash is all zeros), so it stays explicitly unchecked. The general PlacementResult decoder and the test fixtures are unchanged. This is defence in depth. Nothing shows the current server sending an answer for another scene. ResultBindingTests is built from a real server answer to the synthetic replay. It covers a matching answer, a mismatch, an absent, null or numeric hash, an uppercase, short or non-hex hash, a body that isn't JSON, and a mismatched answer carrying an unparseable number elsewhere. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Note Review skipped Codemetrics is installed on this repository, but it hasn't been added to your team yet. To enable AI code reviews for this repo, go to your dashboard and add BaseScanning to your team's repositories. |
|
ScanEngine logs an upload failure's description with public privacy. ResultBinding.Refusal's description included the full SHA-256 of the scene sent, and for a malformed value the server's declared text (Greptile on #198). A scene's hash fingerprints the homeowner's scan, so it shouldn't be in the device log. The description now says what went wrong and, for a malformed value, its length. The hashes stay in the enum cases for callers and tests. aRefusalsDescriptionNamesNoHash checks all three refusal kinds. Co-Authored-By: Claude Code <noreply@anthropic.com>
Stacked on #191 (
codex/public-assembly-20260930). Addresses the response-binding half of #144; it doesn't close the issue. The local-network purpose-string half stays open there.What changed
The placement client decoded the answer's
stats.input_sha256but never compared it with the scene it had sent. NowHTTPResultClient.submitrunsResultBinding.checkon every 2xx answer before returning it. An answer is used only when it declares the SHA-256 of the exact bytes this phone sent.The server hashes the scene bytes it parses:
server/api.py_parsepasses the bare scene.json body toparse_scene, andserver/scene.py:751hashes it.server/schemas/result.schema.jsonrequires the field as 64 lowercase hex digits. The check fails closed:A refused answer is never shown. It goes through the existing "We couldn't read the server's answer" recovery (
UploadFailureKind.unreadableAnswer), which leads back to the review and isn't retried.SampleResultClientstays unchecked, and its comment now says so. The bundled sample answers no particular scene, since its hash is all zeros. The generalPlacementResultdecoder and the UI-test fixtures are unchanged. The UI tests use the sample client, a real server throughHOUSESCAN_SERVER_URL, or-uiDemoResultFile, which never goes throughHTTPResultClient.Why
This is defence in depth. A server answer is only useful for the scene it was computed from, and the field to prove that already existed. Nothing shows the current server sending an answer for another scene.
Verification
ResultBindingTestsis built from a real server answer to the synthetic replay. It covers:Every refusal classifies as
unreadableAnswerand isn't retryable.Locally, through the shared QA queue: HouseScanKit passed 650/650 tests in 92 suites, and the app built (
xcodebuild … -destination generic/platform=iOS, no signing).No UI test was run locally. No phone or live server was used.
Risk
stats.input_sha256is now refused instead of shown. The result schema already requires the field, and the deployed server sends it.0b35f65c.Checklist
🤖 Generated with Claude Code