Repository navigation
[obvious-fleet] Backend burst8 integration: one command replays the supported capture packet end to end #277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
bad040c
fix(backend): make the case suite reject invalid manifests and forged…
SamGu-NRX 83d93f0
Integrate burst8-bs-replay-acceptance runner enforcement (producer 5f…
SamGu-NRX a73c5c0
Integrate burst8-bs-supported-case-pairs packet (producer e7ad2abc, s…
SamGu-NRX b4d28f4
Reconcile twin-identity manifest with the hardened case loader
SamGu-NRX 250c043
Reconcile packet acceptance with the real merge and bridge contracts
ObviousApp 3dbe72b
Add end-to-end CLI acceptance tests for the supported packet
ObviousApp ba7eba2
Keep legacy-manifest CLI tests on the explicit legacy manifest
ObviousApp 596ed9a
fix(backend): import the #258/#262 receipt fixes; a crashed run or re…
SamGu-NRX 54a5a5b
fix(backend): restore the run_cases dev env; the flags must not leak …
SamGu-NRX b9032a7
fix(backend): sensitivity coverage bands are validated but are not ob…
SamGu-NRX a993208
fix(backend): materialize missing stage records so an older unfinishe…
SamGu-NRX c4632e3
fix(backend): placement refuses objects_3d artifacts without an expli…
SamGu-NRX e2376df
fix(backend): supported capture fixtures carry their observed coverag…
SamGu-NRX 1327752
fix(backend): stage-record recovery claims are atomic, so a racing co…
SamGu-NRX fac367e
Merge origin/alpha into obv/burst8-bs-capture-path-integration
ObviousApp c3e479b
fix(backend): the legacy manifest's cross-capture now evaluates on th…
ObviousApp 31ca9ef
fix(backend): claim only the stage records the tick found missing
SamGu-NRX df1b2b5
fix(backend): one stage-claim lock instead of a lock per run and stage
SamGu-NRX 380bb27
Merge origin/alpha into obv/burst8-bs-capture-path-integration
SamGu-NRX 3daefe4
test(backend): the tap case merges exactly one vent on every committe…
SamGu-NRX 2e469fc
fix(backend): a late correction does not reopen a run without stage r…
SamGu-NRX File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| """Stage-record recovery claims are atomic: a record another coordinator | ||
| materialized and started inside the race window is returned untouched, never | ||
| reset to pending with its attempt and worker identity stripped.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import threading | ||
|
|
||
| from server.common.backends.state_memory import MemoryState | ||
| from server.common.backends.state_modal_dict import ModalDictState | ||
| from server.contracts.jobs import StageRecord | ||
|
|
||
|
|
||
| def _rec(run_id: str, stage: str, worker: str | None = None) -> StageRecord: | ||
| return StageRecord( | ||
| run_id=run_id, | ||
| stage=stage, | ||
| status="pending", | ||
| attempt=1, | ||
| heartbeat="2026-10-16T00:00:00Z", | ||
| worker_call_id=worker, | ||
| ) | ||
|
|
||
|
|
||
| def test_memory_claim_returns_the_existing_record_untouched() -> None: | ||
| state = MemoryState() | ||
| first = state.create_stage_if_absent(_rec("run", "poses", worker="left")) | ||
| assert first.worker_call_id == "left" | ||
|
|
||
| second = state.create_stage_if_absent(_rec("run", "poses", worker="right")) | ||
|
|
||
| assert second.worker_call_id == "left", "the loser must read back the winner" | ||
| stored = state.get_stage("run", "poses") | ||
| assert stored.worker_call_id == "left" and stored.attempt == 1 | ||
|
|
||
|
|
||
| def test_memory_claim_inserts_when_absent() -> None: | ||
| state = MemoryState() | ||
| created = state.create_stage_if_absent(_rec("run", "poses")) | ||
| assert created.status == "pending" and created.attempt == 1 | ||
| assert state.get_stage("run", "poses") == created | ||
|
|
||
|
|
||
| def test_modal_claim_two_states_share_one_map() -> None: | ||
| mapping: dict[str, object] = {} | ||
| left, right = ModalDictState(mapping), ModalDictState(mapping) | ||
| winner = left.create_stage_if_absent(_rec("run", "poses", worker="left")) | ||
| loser = right.create_stage_if_absent(_rec("run", "poses", worker="right")) | ||
| assert winner.worker_call_id == "left" | ||
| assert loser.worker_call_id == "left", "the loser must read back the winner" | ||
| assert right.get_stage("run", "poses").worker_call_id == "left" | ||
|
|
||
|
|
||
| def test_modal_claim_many_threads_one_winner() -> None: | ||
| state = ModalDictState() | ||
| results: list[str] = [] | ||
| guard = threading.Lock() | ||
|
|
||
| def claim(worker: str) -> None: | ||
| rec = state.create_stage_if_absent(_rec("run", "poses", worker=worker)) | ||
| with guard: | ||
| results.append(rec.worker_call_id) | ||
|
|
||
| threads = [threading.Thread(target=claim, args=(f"w{i}",)) for i in range(8)] | ||
| for thread in threads: | ||
| thread.start() | ||
| for thread in threads: | ||
| thread.join() | ||
|
|
||
| assert len(set(results)) == 1, f"every claimant must read back the same winner: {results}" | ||
| stored = state.get_stage("run", "poses") | ||
| assert stored.worker_call_id == results[0] and stored.attempt == 1 | ||
|
|
||
|
|
||
| def test_modal_claims_leave_no_per_run_state_in_the_module() -> None: | ||
| """A long-lived worker claims records for many runs. Module-level lock state | ||
| must stay bounded: a lock kept per (run, stage) key grows without limit.""" | ||
| from server.common.backends import state_modal_dict | ||
|
|
||
| state = ModalDictState() | ||
| state.create_stage_if_absent(_rec("warm", "poses")) | ||
|
|
||
| def sizes() -> dict[str, int]: | ||
| return { | ||
| name: len(value) | ||
| for name, value in vars(state_modal_dict).items() | ||
| if isinstance(value, dict) and not name.startswith("__") | ||
| } | ||
|
|
||
| before = sizes() | ||
| for index in range(200): | ||
| state.create_stage_if_absent(_rec(f"run-{index}", "poses")) | ||
|
|
||
| assert sizes() == before |
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
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
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
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For an older finished capture missing any stage record,
on_confirmationsaves the corrected answer, but this guard skips rebuilding the result.GET /resultreads the existing result file, so the user gets a success response while still seeing the previous answer. Preserve the upstream work while rebuildingcriteriaandresult, rather than returning without applying the correction.Prompt To Fix With AI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed. Landed #277 anyway: on alpha before this PR such a run was left stuck in
running, so the guard is strictly better, and the fix (rebuild criteria and result while preserving upstream work, with a failing-first test) is the required first item of burst9-bs-real-downstream-journey, which owns backend/server/orchestrator/api.py. — Apps lead