Skip to content

Backend: compose and serve a real result from hydrated artifacts - #280

Merged
SamGu-NRX merged 4 commits into
alphafrom
obv/burst9-bs-result-published-inputs
Oct 16, 2026
Merged

SamGu-NRX merged 4 commits into
alphafrom
obv/burst9-bs-result-published-inputs

Conversation

@obvious-autobuild

Copy link
Copy Markdown

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 (hydrated reads/reads.json / scale/scale.json fallbacks, storage/settings for reviewer artifact links) plus the new integration suite. Based on obv/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:

  1. Eligible — composer publishes 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.
  2. Needs views — verdict swap produces needs_more_photos with the fixture's view list; device body again matches published bytes.
  3. Hidden reject — with 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.
  4. Malformed verdict — composer raises verdict_invalid; coordinator closes the run with a manual-review envelope. The device sees manual review, never a successful result.
  5. Missing verdicts — same closure contract on verdict_missing.
  6. Controlled verdict change — attempt 2 on the same run with a different verdict changes the served body (eligible → needs_more_photos) while the event log keeps exactly one terminal event per run (emit_events idempotency).
  7. Reviewer surface — the reviewer-gated payload carries a signed verdict URL the device response does not.

Three behaviors the proof surfaced, now pinned by tests:

  • A failure-coded run cannot present eligible. The composer's lattice downgrades eligible to manual review while run.failure_code is 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.
  • A failed result stage still closes through the coordinator, which publishes a manual-review envelope — the device contract degrades to manual review, never to a false success or a raw error.
  • Process-env pollution crosses test boundaries. The placement-case runner writes HSS_ORCH_STAGE_STUBS=1 straight into os.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)

  • New suite: 7 passed (pytest backend/tests/test_result_published_inputs.py), and 16 passed when run behind test_capture_placement_cases.py (the leak check), on the rebased head.
  • Compose suite: 25 passed.
  • ruff check and ruff format --check on the touched files: clean.
  • lint-imports: 5 kept, 0 broken.
  • Full backend suite: 5 failed, 1263 passed, 17 skipped — the known baseline failure set (SSR hygiene + 4 eval gates), unchanged by this branch.

🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact

Human author: Sam Gu (sgu07966@gmail.com)

SamGu-NRX and others added 4 commits October 16, 2026 02:00
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>
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4b773846-aad1-4d67-8042-022cdc3954b1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the size:M 30-99 effective changed lines. label Oct 16, 2026
@SamGu-NRX
SamGu-NRX changed the base branch from obv/burst7-bs-capture-placement-runner to alpha October 16, 2026 05:22
@SamGu-NRX
SamGu-NRX marked this pull request as ready for review October 16, 2026 05:23
@SamGu-NRX
SamGu-NRX merged commit 19398f0 into alpha Oct 16, 2026
3 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T05:26:26.474168Z bff0e82 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@codemetrics-ai

Copy link
Copy Markdown

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.

@greptile-apps

greptile-apps Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] Result composition now accepts storage and settings objects.

Refresh the saved reviewer links before merging; they stop working after their TTL.

Fix All in Claude CodeFindings

  1. P1 Saved reviewer links expire ▶
  2. P2 Real capture photos stay broken ▶
  3. P2 Dropped fields escape the test ▶
Fix with agent prompt
### Issue 1
backend/server/result/compose.py:915-916
`_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.

### Issue 2
backend/server/result/compose.py:860-865
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`.

### Issue 3
backend/tests/test_result_published_inputs.py:305-310
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.

Summary

The PR lets the result composer read hydrated reads and scale artifacts and adds signed links to the reviewer page.

  • The result stage uses hydrated reads and scale files from its work folder.
  • Reviewer pages link to stored photos and run artifacts.
  • A journey test follows real composed results through the API.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Stored upstream artifacts] --> B[Hydrated stage files]
  B --> C[Real result composer]
  C --> D[Published result JSON]
  D --> E[Device and reviewer result routes]
  C --> F[Saved review HTML with signed links]
  F --> G[Reviewer page]
  G --> H[Expired links after TTL]
Loading

Reviews (1) · Last reviewed commit: "test: pin real orchestrator stages for t..." · Reviewed by Greptile

Comment on lines +915 to +916
for name, key in zip(names, keys, strict=False):
links.append(_signed_url(storage, settings, key) or name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

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.

Fix in Claude Code Fix in Codex

Comment on lines +860 to +865
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Fix in Claude Code Fix in Codex

Comment on lines +305 to +310
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}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
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.

Fix in Claude Code Fix in Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 effective changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants