Skip to content

feat(orchestrator): dev-only scripted local outcomes for stage stubs - #291

Draft
obvious-autobuild[bot] wants to merge 4 commits into
alphafrom
obv/burst11-bs-local-outcome-states
Draft

obvious-autobuild[bot] wants to merge 4 commits into
alphafrom
obv/burst11-bs-local-outcome-states

Conversation

@obvious-autobuild

@obvious-autobuild obvious-autobuild Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

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

What

A dev-only way to walk a local run into each terminal state the app renders, with no Modal or GPU: set HSS_LOCAL_OUTCOME to one of manual_review (default), eligible, needs_more_photos, not_eligible, or failed, and the stage stubs script the run to that state. HSS_LOCAL_STAGE_DELAY_S (0–30 s) makes each stub sleep so the app shows processing events.

HSS_LOCAL_OUTCOME Capture status Notes
unset / manual_review (default) manual_review Today's stub run, byte-identical.
eligible complete Result carries a proposed placement, worded as proposed.
needs_more_photos needs_views One scripted band view; the app sends a retake request.
not_eligible complete Needs SHOW_REJECT_TO_HOMEOWNER=1; without it the criteria stub refuses and the run fails loudly, leaving the capture on manual_review.
failed failed The validate stub fails the run onto outcome row 1 (validation stand-in imagesUsable: false).

Both variables are refused loudly — with an error naming the variable, at config load — when stage stubs are off, the environment is not dev, the value is unknown, or the number is out of range. Scripted messages and review pages always say it is a local test outcome, not a check of a wall.

How it is pinned

tests/test_local_outcome_states.py drives every state through the real API front door (create → upload → commit → finalize → events → result) and asserts capture status, served result, outcome kind, and the terminal event kind against the orchestrator outcome table (decide()). The same module regenerates tests/fixtures/app_states/ /{result,events}.json — committed evidence that is re-validated against the outcome table on every run. Refusals are verified at dispatch time (no run created, capture stays non-terminal, hook error logged). The failed state's device envelope keeps outcome kind manual_review with message packet_invalid — capture failed, device sees the degraded review envelope.

Review-thread fixes (437a105)

Fixes the four #291 review findings and #293's validate-artifact finding, each with a failing-first regression test:

  • Run-scoped validation evidence (Backend user run: drive the local API through every app state over a real socket #293): the scripted failed run registered packet/validation.json under the bare logical name, so a later failed capture overwrote an earlier run's evidence. The bytes now land at the run-prefixed storage key runner._publish uses (captures/{capture_id}/runs/{run_id}/packet/validation.json), resolved through the run record's artifact map — no coordinator change needed. Regression: two consecutive failed captures each end failed with their own validation.json at their own key.
  • Epoch ids from packet objects: the stub read packet epochs as strings, but the committed mini-arkit packet stores epoch objects ({"id": "e1"}), so _packet_epochs returned [] and eligible placements never carried arkitEpoch/boxArkitWorld. Now reads the ids the way the real composer feeds ar.epoch_id. Regression: the eligible result carries arkitEpoch == "e1" with a non-null ARKit-world box (committed fixtures regenerated).
  • Profile footprints: the scripted placement hardcoded the profile-C footprint while advertising the configured profile. It now uses the configured profile's row in the real composer's FOOTPRINT table (server/result/compose.py). Regression: eligible footprints for profiles A/B/C match FOOTPRINT[profile].
  • Finite stage delay: HSS_LOCAL_STAGE_DELAY_S=nan/inf passed float() and was refused only by the chained range comparison (with the misleading range message). Now refused by name as non-finite before the range check. Regression pins the finite-specific message.

Coordinator hardening note (from the P1 thread, no coordinator edit in this PR): a first-attempt validate failure and a retry failure both decide on a later scheduler tick, so the row-1 decision is deterministic today (verified: attempt 1 fails → _mark_error tick 1; retry fails → tick 2 polls with the artifact registered in tick 1's put_run). The robustness fix, if wanted, is in server/orchestrator/coordinator.py _read_json: in the except FileNotFoundError branch after _ensure_run_fresh(run), re-check the run record once more before accepting absent:

# after run = _ensure_run_fresh(...) refresh
if key in run.artifacts:
    return _load(state, storage, run, name)

Verification

  • tests/test_local_outcome_states.py: 19 passed (was 14; +5 regression tests).
  • Stub-flow regressions (test_capture_fixture_flow.py, test_runtime_join_journey.py): 22 passed total with the new suite.
  • Full tests/: 201 passed, 1 skipped, 14 errors — the errors are the pre-existing load_receipt import collection errors in test_capture_cold_restart.py, present on the alpha base (stacked-PR base artifact), not touched by this branch.
  • ruff check / ruff format --check clean on touched files; lint-imports 5 kept, 0 broken.
  • No production behavior changes without the variables set: default outcome is byte-identical to today's stub run (covered by the default-state test and committed fixture).

Draft — ready for review.

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

HSS_LOCAL_OUTCOME walks a stubbed local run to each state the app renders:
manual_review (default, today's bytes), eligible, needs_more_photos,
not_eligible (needs SHOW_REJECT_TO_HOMEOWNER=1), failed (validate fails onto
outcome row 1). HSS_LOCAL_STAGE_DELAY_S paces stub stages 0-30s. Both are
refused loudly outside dev stage stubs; scripted messages and the review page
say the outcome is a local test, not a check of a wall.
…state fixtures

Walk every HSS_LOCAL_OUTCOME state through the real API front door and
commit the served payloads plus event streams as fixtures, validated
against the orchestrator outcome table on every run.

- config: keep OrchConfig field declarations in place (lost in an edit race)
- stubs: restore scripted_outcome helpers in _common; terminal-event
  selection and fixture regeneration fixes in the test module
- fixtures: one directory per rendered state (manual_review, eligible,
  needs_more_photos, not_eligible, failed) with result.json + events.json
- README: correct where not-eligible refusal and setting validation happen
@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: aebddf4f-4d12-442e-bfe3-2bc7ce745d4c

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:L 100-499 effective changed lines. label Oct 16, 2026
obvious-autobuild Bot added a commit that referenced this pull request Oct 16, 2026
…re API base

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner

@codex review

@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-16T18:19:50.613441Z 437a105 Manual request
ℹ️ 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d7d0dde1e

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread backend/server/orchestrator/stubs/result.py Outdated
Comment thread backend/server/orchestrator/config.py
Comment on lines +45 to +48
)
ctx.storage.put_bytes(key, body, "application/json")
run = ctx.state.get_run(ctx.run_id)
run.artifacts[key] = 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.

P1 Badge Persist the scripted validation artifact before failure handling

The failing stub updates a separate RunRecord copy and then raises. The coordinator handles the exception with its already-held run object, so _images_usable(self.s, run) cannot see this newly registered artifact and defaults to True; decide() consequently selects the validate-failed/images-usable row (manual review) instead of the intended row 1 (failed) for HSS_LOCAL_OUTCOME=failed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Verified against the current code: the row-1 outcome lands deterministically. The validate stage retries (max_attempts: 2), and both the first failure and the retry failure are decided on later scheduler ticks that poll the run record after the stub's put_run registered the artifact — so the capture ends failed with the device envelope manual_review/packet_invalid, which the focused test pins. 437a105 also moved the scripted validation bytes to the run-prefixed key runner._publish uses, so consecutive failed captures each keep their own evidence. The registration-timing robustness fix belongs in coordinator._read_json (re-check run.artifacts after the fresh-run reload); the exact change is noted in the PR body. Leaving this open for that coordinator-touching wave.

Comment thread backend/server/orchestrator/stubs/result.py
SamGu-NRX and others added 2 commits October 16, 2026 17:48
The scripted local-outcome stubs drift from the behaviors they stand in
for, in four ways the review threads on PR #291/#293 name:

- The scripted failed run registered packet/validation.json under the
  bare logical name, so a later failed capture overwrote the earlier
  run's evidence. The bytes now land at the run-prefixed key
  runner._publish uses, resolved through the run record's artifact map.
- The stub read packet epochs as strings, but the committed mini-arkit
  packet stores epoch objects ({"id": "e1"}), so eligible placements
  never carried arkitEpoch/boxArkitWorld. Now read the ids the way the
  real composer feeds ar.epoch_id.
- The scripted placement hardcoded the profile-C footprint; it now uses
  the configured profile's row in the real composer's FOOTPRINT table.
- HSS_LOCAL_STAGE_DELAY_S=nan/inf now refuse by name as non-finite
  before the range comparison.

Regression tests: run-scoped validation evidence across two consecutive
failed captures, per-profile footprints for A/B/C, the finite refusal,
and the eligible result carrying epoch e1 with an ARKit-world box.
Committed app-state fixtures regenerated from the fixed stubs.

Co-authored-by: Obvious <noreply@obvious.ai>
@obvious-autobuild

Copy link
Copy Markdown
Author

Pushed 437a105 fixing all four review findings on this PR plus the validation-artifact namespacing finding on #293:

  • Run-scoped packet/validation.json: bytes now land at the run-prefixed key runner._publish uses and register through the run record's artifact map, so consecutive failed captures keep their own evidence (also resolves Backend user run: drive the local API through every app state over a real socket #293's namespacing thread).
  • Packet epochs are read as ids from epoch records ({"id": "e1"}) the way the real composer feeds ar.epoch_id; the eligible result now carries arkitEpoch: "e1" and a non-null boxArkitWorld (fixtures regenerated).
  • The scripted placement uses the configured profile's row in the real composer's FOOTPRINT table (A/B/C covered by tests).
  • HSS_LOCAL_STAGE_DELAY_S=nan/inf refuse by name as non-finite before the range check.

Each fix has a failing-first regression test; focused suite 19 passed, ruff and lint-imports clean. Full make test: 1,528 passed / 16 skipped, with only the known baseline (4 SD-96 eval-gate failures, 14 pre-existing cold-restart collection errors — identical on alpha). The P1 registration-timing thread stays open with verification evidence; the exact coordinator._read_json hardening change is in the PR body.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 437a10595e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@SamGu-NRX

Copy link
Copy Markdown
Owner

@greptileai review this at plus

@greptile-apps

greptile-apps Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5 Tier: plus

[Medium impact] Dev-only scripted test outcomes for local orchestrator stubs.

This appears safe to merge, with non-blocking fixes recommended for the local test paths.

Fix All in Claude CodeFindings

  1. P2 Retakes use the wrong event ▶
  2. P2 Placement fields disagree ▶
  3. P2 Failed runs ignore the delay ▶
  4. P2 Review page mislabels real work ▶
Fix with agent prompt
### Issue 1
backend/server/orchestrator/stubs/result.py:81-85
With `HSS_LOCAL_OUTCOME=needs_more_photos`, the stub still emits `verdict_ready`. Both `decide()` row 13 and the real composer use `retake_request`, with the requested view IDs. Local runs therefore skip that event's handler, and the new tests and fixture lock in the difference. Emit `retake_request` for this outcome and compare the terminal event against `decide()` in the tests.

### Issue 2
backend/server/orchestrator/stubs/result.py:150
The placement center still uses profile C's hardcoded width. For profile A or B, `width` is `0.6096`, so `startSM=0.3` requires a center of `0.6048`. The current center, `0.6937`, puts the box's near edge at `0.3889` instead. Calculate the center from the same start and width used in the returned placement.

```suggestion
    center = [0.3 + width / 2, height / 2, depth / 2 + _STANDOFF_M]
```

### Issue 3
backend/server/orchestrator/stubs/validate.py:15-17
`HSS_LOCAL_OUTCOME=failed` bypasses `write_files()`, which contains the only scripted sleep. Setting `HSS_LOCAL_STAGE_DELAY_S` therefore has no effect on this outcome: both validation attempts fail immediately. Developers cannot use the delay to see validation in progress before failure. Apply the shared delay before branching into `_script_failed()`, without sleeping twice on successful validation.

### Issue 4
backend/server/orchestrator/stubs/result.py:232-235
The new review page says every stage was a stand-in and includes `placement` in that list. But `placement` has `stubs_exempt=True`, so the runner executes the real solver even when stage stubs are enabled. List the stages that were actually stubbed and identify `placement` as real CPU work, so someone checking what ran gets an accurate answer.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds dev-only scripted outcomes and optional stage delays, with API-flow tests and saved result/event fixtures.

  • Local capture runs can end on each scripted app outcome.
  • A scripted validation failure ends a local capture as failed.
  • Local stub stages can pause to show processing over time.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Load local configuration] --> B{Dev environment and stage stubs?}
    B -->|No, local variable supplied| C[Refuse configuration]
    B -->|Yes| D{Selected outcome}
    D -->|failed| E[Write run-scoped validation evidence]
    E --> F[Fail validation and close capture]
    D -->|Other outcomes| G[Script criteria verdict]
    G --> H[Compose stub result]
    H --> I[Apply outcome table to capture status]
Loading

Reviews (1) · Last reviewed commit: "fix(orchestrator): stub outcomes hold re..." · Reviewed by Greptile

Comment on lines +81 to 85
kind = "manual_review" if outcome == "manual_review" else outcome
ctx.state.append_event(
ctx.capture_id,
"verdict_ready",
{"runId": ctx.run_id, "kind": kind},

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 Retakes use the wrong event

With HSS_LOCAL_OUTCOME=needs_more_photos, the stub still emits verdict_ready. Both decide() row 13 and the real composer use retake_request, with the requested view IDs. Local runs therefore skip that event's handler, and the new tests and fixture lock in the difference. Emit retake_request for this outcome and compare the terminal event against decide() in the tests.

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/server/orchestrator/stubs/result.py
Line: 81-85

Comment:
**Retakes use the wrong event**

With `HSS_LOCAL_OUTCOME=needs_more_photos`, the stub still emits `verdict_ready`. Both `decide()` row 13 and the real composer use `retake_request`, with the requested view IDs. Local runs therefore skip that event's handler, and the new tests and fixture lock in the difference. Emit `retake_request` for this outcome and compare the terminal event against `decide()` in the tests.

---

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

"""
width, height, depth = FOOTPRINT.get(profile, FOOTPRINT["C"])
size = [width, height, depth]
center = [0.6937, height / 2, depth / 2 + _STANDOFF_M]

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 Placement fields disagree

The placement center still uses profile C's hardcoded width. For profile A or B, width is 0.6096, so startSM=0.3 requires a center of 0.6048. The current center, 0.6937, puts the box's near edge at 0.3889 instead. Calculate the center from the same start and width used in the returned placement.

Suggested change
center = [0.6937, height / 2, depth / 2 + _STANDOFF_M]
center = [0.3 + width / 2, height / 2, depth / 2 + _STANDOFF_M]
Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/server/orchestrator/stubs/result.py
Line: 150

Comment:
**Placement fields disagree**

The placement center still uses profile C's hardcoded width. For profile A or B, `width` is `0.6096`, so `startSM=0.3` requires a center of `0.6048`. The current center, `0.6937`, puts the box's near edge at `0.3889` instead. Calculate the center from the same start and width used in the returned placement.

```suggestion
    center = [0.3 + width / 2, height / 2, depth / 2 + _STANDOFF_M]
```

---

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 15 to +17
def run(ctx: StageContext) -> StageResult:
if scripted_outcome() == "failed":
_script_failed(ctx)

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 Failed runs ignore the delay

HSS_LOCAL_OUTCOME=failed bypasses write_files(), which contains the only scripted sleep. Setting HSS_LOCAL_STAGE_DELAY_S therefore has no effect on this outcome: both validation attempts fail immediately. Developers cannot use the delay to see validation in progress before failure. Apply the shared delay before branching into _script_failed(), without sleeping twice on successful validation.

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/server/orchestrator/stubs/validate.py
Line: 15-17

Comment:
**Failed runs ignore the delay**

`HSS_LOCAL_OUTCOME=failed` bypasses `write_files()`, which contains the only scripted sleep. Setting `HSS_LOCAL_STAGE_DELAY_S` therefore has no effect on this outcome: both validation attempts fail immediately. Developers cannot use the delay to see validation in progress before failure. Apply the shared delay before branching into `_script_failed()`, without sleeping twice on successful validation.

---

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 +232 to +235
stages = ", ".join(STAGE_DEPS)
return (
"<html><body><h1>Local stage-stub run</h1>"
f"<p>Every stage here is a stand-in: stage stubs ran instead of real processing ({stages}).</p>"

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 Review page mislabels real work

The new review page says every stage was a stand-in and includes placement in that list. But placement has stubs_exempt=True, so the runner executes the real solver even when stage stubs are enabled. List the stages that were actually stubbed and identify placement as real CPU work, so someone checking what ran gets an accurate answer.

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/server/orchestrator/stubs/result.py
Line: 232-235

Comment:
**Review page mislabels real work**

The new review page says every stage was a stand-in and includes `placement` in that list. But `placement` has `stubs_exempt=True`, so the runner executes the real solver even when stage stubs are enabled. List the stages that were actually stubbed and identify `placement` as real CPU work, so someone checking what ran gets an accurate answer.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

This branch has not been deployed

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

Labels

size:L 100-499 effective changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants