Repository navigation
Backend: compose and serve a real result from hydrated artifacts - #280
Conversation
Scoped backend/server/result/compose.py and test_compose.py changes from #255 (producer head 6cb76b1, applied three-way onto merge-base 2d0911f of obv/burst7-bs-capture-placement-runner). The composer falls back to the hydrated work_dir/reads and work_dir/scale copies when short input keys are absent, and receives storage/settings to sign reviewer verdict links. Co-Authored-By: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…erves Journey tests drive the real capture front door (create/register/upload/ commit/finalize), seed fixture-authored upstream scene/verdicts/reads/scale artifacts at the production storage keys, hydrate them through Services.stage_context, run server.result.compose.run via runner.run_stage with stubs disabled, and complete through the real coordinator. The route is then served as a device and as a reviewer. Covered: eligible, needs_views, hidden-reject masking with a preserved internal decision, malformed and missing verdicts closed to failed with a not-ready envelope, reviewer-signed verdict URLs with device redaction, and a controlled verdict swap changing the served response. A private canary in the verdict blob never reaches the served body. No result/result.json is seeded anywhere. Co-Authored-By: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…t filter, manual-review closure) The journey now pins three production behaviors surfaced while proving served == published: the composer's lattice refuses eligible while run.failure_code is set (fixture resets the run to a genuinely clean in-flight state); a failed result stage still closes through the coordinator, which publishes a manual-review envelope rather than leaving the device without a result; and emit_events keeps exactly one terminal event per run even after a re-compose, so final_events counts the composer's own final types only. The served-vs-stored comparison excludes the route's five composed keys from both sides and reports the exact differing fields. Co-authored-by: Sam Gu <sgu07966@gmail.com>
The placement-case runner writes HSS_ORCH_STAGE_STUBS=1 straight into os.environ, and that write outlives its test. With the leaked flag the finalize thread's coordinator tick runs stubbed stages, which publishes a stub result and a verdict_ready event before the fixture ever runs — breaking the needs-views event expectation and the no-composed-event contract for malformed/missing verdicts. Pin the flag to real stages so the journey is hermetic against process-env pollution. Co-authored-by: Sam Gu <sgu07966@gmail.com>
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configuration
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. |
[Medium impact] Result composition now accepts storage and settings objects. Refresh the saved reviewer links before merging; they stop working after their TTL.
|
| for name, key in zip(names, keys, strict=False): | ||
| links.append(_signed_url(storage, settings, key) or name) |
There was a problem hiding this comment.
_review_links saves signed URLs in review.html, but the review route serves that same HTML without refreshing them. On volume, R2, and GCS storage, reviewers opening the page after the TTL—15 minutes by default—get expired file links. Signed photos expire too, and reloading does not help.
Generate fresh links when the reviewer opens the page, or use reviewer-gated download routes.
Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/server/result/compose.py
Line: 915-916
Comment:
**Saved reviewer links expire**
`_review_links` saves signed URLs in `review.html`, but the review route serves that same HTML without refreshing them. On volume, R2, and GCS storage, reviewers opening the page after the TTL—15 minutes by default—get expired file links. Signed photos expire too, and reloading does not help.
Generate fresh links when the reviewer opens the page, or use reviewer-gated download routes.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| image = kf.get("image") | ||
| url = None | ||
| if image: | ||
| photo_key = f"captures/{capture.capture_id}/{image}" | ||
| if storage is not None and getattr(storage, "exists", lambda _k: False)(photo_key): | ||
| url = _signed_url(storage, settings, photo_key) |
There was a problem hiding this comment.
Real capture photos stay broken
The new photo signing expects image, but _retarget_keyframes writes real keyframes without that field. The scene stage saves them without adding it. Each photo therefore still falls back to a bare keyframe ID, which the page uses as a broken relative image URL.
Resolve IDs to the capture's packet image paths, and test that real shape rather than only a fixture containing image.
Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/server/result/compose.py
Line: 860-865
Comment:
**Real capture photos stay broken**
The new photo signing expects `image`, but `_retarget_keyframes` writes real keyframes without that field. The scene stage saves them without adding it. Each photo therefore still falls back to a bare keyframe ID, which the page uses as a broken relative image URL.
Resolve IDs to the capture's packet image paths, and test that real shape rather than only a fixture containing `image`.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| differing = { | ||
| key: {"stored": rest_stored.get(key), "served": rest_served.get(key)} | ||
| for key in sorted(set(rest_served) | set(rest_stored)) | ||
| if rest_served.get(key) != rest_stored.get(key) | ||
| } | ||
| assert not differing, f"the route served bytes the composer did not publish: {differing}" |
There was a problem hiding this comment.
Dropped fields escape the test
The published-body comparison treats a missing key as equal to a key whose value is null: both .get() calls return None. Removing a null-valued field from the response therefore still passes this proof of equality.
Compare rest_served and rest_stored directly, or separately assert that their key sets match.
| differing = { | |
| key: {"stored": rest_stored.get(key), "served": rest_served.get(key)} | |
| for key in sorted(set(rest_served) | set(rest_stored)) | |
| if rest_served.get(key) != rest_stored.get(key) | |
| } | |
| assert not differing, f"the route served bytes the composer did not publish: {differing}" | |
| assert rest_served == rest_stored, "the route served a body that differs from the published document" |
Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/tests/test_result_published_inputs.py
Line: 305-310
Comment:
**Dropped fields escape the test**
The published-body comparison treats a missing key as equal to a key whose value is `null`: both `.get()` calls return `None`. Removing a null-valued field from the response therefore still passes this proof of equality.
Compare `rest_served` and `rest_stored` directly, or separately assert that their key sets match.
```suggestion
assert rest_served == rest_stored, "the route served a body that differs from the published document"
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
What ships
Proof that the production result composer publishes the exact bytes the API serves. The new journey test (
backend/tests/test_result_published_inputs.py) drives the real capture front door and the real result stage — no directly seeded result JSON, no stub composer — then asserts the GET response is built from the composer's published bytes.Branch
obv/burst9-bs-result-published-inputs= #255's scoped composer changes (hydratedreads/reads.json/scale/scale.jsonfallbacks, storage/settings for reviewer artifact links) plus the new integration suite. Based onobv/burst7-bs-capture-placement-runner@c255a235.The journey
Each test runs one full capture through the actual FastAPI app (memory state, local-volume storage, static test auth, upload HMAC secret), lets the finalize thread drive the real coordinator tick (which, with no VLM model configured, closes the run through the early gates), then re-registers every upstream stage as done with fixture-authored artifacts and runs the real composer end to end:
result/result.json; the device GET serves those bytes plus exactly the route's five composed keys (status, runId, signed verdict/preview URLs, review URL). Verified field-by-field against the stored bytes.needs_more_photoswith the fixture's view list; device body again matches published bytes.SHOW_REJECT_TO_HOMEOWNER=false, a rejecting verdict masks the device decision to manual review, empties reasons, hides failed criteria, nulls the signed verdict URL; the internal document keeps the true decision, and the canary string never appears in any device-visible body.verdict_invalid; coordinator closes the run with a manual-review envelope. The device sees manual review, never a successful result.verdict_missing.emit_eventsidempotency).Three behaviors the proof surfaced, now pinned by tests:
run.failure_codeis set — the fixture's run reset must produce a genuinely clean in-flight run because the fixture boundary re-registers every upstream stage (dense included) as done.HSS_ORCH_STAGE_STUBS=1straight intoos.environ, and that write outlives its test: the finalize thread's tick then runs stubbed stages and publishes a stub result before the fixture ever runs. The journey pins the flag to real stages, making it hermetic.Verification (only checks actually run)
pytest backend/tests/test_result_published_inputs.py), and 16 passed when run behindtest_capture_placement_cases.py(the leak check), on the rebased head.ruff checkandruff format --checkon the touched files: clean.lint-imports: 5 kept, 0 broken.🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact
Human author: Sam Gu (sgu07966@gmail.com)