From 10fdac4860035fd5c845a621752e93688e2e674e Mon Sep 17 00:00:00 2001
From: codex-scheduled
Date: Tue, 11 Aug 2026 08:08:13 -0700
Subject: [PATCH] feat(rate-of-closure): complete regional result readback
---
AGENT_HANDOFF.md | 23 +++++++
SPEC.md | 21 +++++-
.../RATE_OF_CLOSURE_CAMPAIGN_HANDOFF.md | 23 +++++++
docs/release/rate_of_closure_campaign.v1.json | 17 ++++-
src/rate_of_closure/AGENT_HANDOFF.md | 22 +++++++
.../regional_execution_readback.py | 65 +++++++++++++++++++
.../ui/pyqt6/regional_execution_evidence.py | 62 ++++++++++++++----
.../RegionalExecutionEvidencePanel.test.tsx | 8 ++-
.../RegionalExecutionEvidencePanel.tsx | 62 +++++++++++++++---
.../model/regionalExecutionReadback.test.ts | 37 +++++++++++
.../src/model/regionalExecutionReadback.ts | 47 ++++++++++++++
.../test_regional_execution_readback.py | 48 ++++++++++++--
12 files changed, 403 insertions(+), 32 deletions(-)
diff --git a/AGENT_HANDOFF.md b/AGENT_HANDOFF.md
index ec690d148..9cbb228b3 100644
--- a/AGENT_HANDOFF.md
+++ b/AGENT_HANDOFF.md
@@ -968,3 +968,26 @@ independent review is complete. UI construction of a qualified ground request
and settled bounce prefix, executor invocation, playback, measured
calibration, compiled regional physics, downstream parity, protected evidence,
release, and issue completion remain open.
+
+## 2026-08-11 complete regional result readback continuation
+
+Branch `feat/4271-regional-result-readback` is a local unpublished child of
+exact draft PR #4351 head
+`fe463b5503a8c7b599a329da18bb690d008871cd`. It extends the matched import-only
+PyQt6/React readback to every qualified summary/result field required for
+honest user inspection: distinct carry/bounce/skid/roll/surface-path/total,
+final downrange/offline, bounce count, ground time, terminal completion, model
+and surface provider IDs/versions, calibration evidence, observed phases,
+typed warnings, executor provenance, and qualification limits.
+
+Null-result cancellation/failure keeps ground-only values unavailable. Partial
+evidence retains the censored-endpoint warning and is not relabeled as rest.
+No physics, executor invocation, trajectory/event tables, playback, compiled
+parity, calibration workflow, or downstream integration is added.
+
+Exact local gates are green: 208 expanded Python ground/plan/PyQt/layout tests,
+111 React files / 691 tests, strict MyPy, Ruff/format, TypeScript type-check,
+zero-warning ESLint, the 202-module production build, campaign-manifest
+validation plus eight manifest tests, documentation governance, module-size
+budget, placeholder scan, and diff checks. The build retains the inherited
+500 kB chunk advisory. Independently review before any GitHub write.
diff --git a/SPEC.md b/SPEC.md
index f0392bb61..8053f551a 100644
--- a/SPEC.md
+++ b/SPEC.md
@@ -26,8 +26,8 @@
| **Owner** | D-sorganization |
| **Primary Language(s)** | Python 3.11+, Rust, JavaScript, TypeScript |
| **License** | MIT |
-| **Current Version** | 1.14.35 |
-| **Spec Version** | 1.14.35 |
+| **Current Version** | 1.14.36 |
+| **Spec Version** | 1.14.36 |
| **Last Spec Update** | 2026-08-11 |
## 2. Purpose & Mission
@@ -46,6 +46,22 @@ Comprehensive monorepo housing 45+ utility tools for data processing, scientific
## 3. Goals & Non-Goals
+### 2026-08-11 Complete Regional Execution Result Readback
+
+- The matched PyQt6 and React evidence readers project every already-qualified
+ ground summary field without recomputation: carry, bounce-air, skid, roll,
+ surface-path, total, final downrange/offline, bounce count, ground time,
+ terminal completion, model and base-surface provider identities, calibration
+ evidence, ordered observed phases, typed warnings, executor provenance, and
+ qualification limitations.
+- Null-result cancellation/failure evidence keeps all ground-only values
+ unavailable and emits no fabricated phases, warnings, calibration, times, or
+ distances. Partial results retain their censored-endpoint warning and are not
+ presented as final-rest outcomes.
+- PyQt6 uses a readable scrollable readback while React uses an accessible
+ definition list and warning list. Both remain import-only consumers of the
+ frozen Python executor envelope; this slice does not add playback or physics.
+
### 2026-08-11 Regional Execution Evidence Readback
- Matched PyQt6 and React regional-plan surfaces can import a bounded strict
@@ -2978,6 +2994,7 @@ Active development with stable core, continuous tool expansion, and web API in p
| Date | Version | Changes |
| ---- | ------- | ------- |
+| 2026-08-11 | 1.14.36 | feat(ground-roll, #4267 #4271): complete the matched PyQt6/React import-only result projection with carry, bounce-air, skid, roll, surface-path, total, final position/offline, bounce count, ground time, terminal completion, model and surface-provider identity/version, calibration evidence, ordered observed phases, typed warnings, executor provenance, and qualification limits; preserve unavailable ground-only values for null-result cancellation/failure and keep partial censored endpoints explicit; retain UI executor invocation, trajectory/event tables, playback, calibration workflows, compiled parity, downstream parity, protected evidence, release, and issue completion as open work. |
| 2026-08-11 | 1.14.35 | feat(ground-roll, #4267 #4271): add matched PyQt6 and React bounded strict import/readback for Python-produced regional execution evidence; require its embedded plan to exactly match the currently valid visible plan, preserve prior accepted evidence after failed imports, invalidate stale evidence after plan edits, expose status/termination/model/skid/roll/total/transitions/provenance, and state explicitly that React executes no regional physics; retain UI executor invocation, playback, measured calibration, compiled physics, downstream parity, protected evidence, release, and issue completion as open work. |
| 2026-08-11 | 1.14.34 | chore(ground-roll, #4271): normally reconcile exact reviewed regional-execution child `012cdfc33ad1590f31a1cbb109f0b8bee8eee700` with exact newly published PR #4342 parent `c1f47f2ef68b3db102da5416aaac17a40f675207` without changing the intended `feat/4274-regional-plan-io` base or rewriting either branch; retain the remediated embedded-plan execution/provenance contract and executor-produced cross-runtime evidence together with canonical request I/O, the bounded engineering-input helper, and verbatim parent/child handoff and SPEC history; keep UI execution/playback, compiled regional physics, downstream parity, protected evidence, PR publication, release, and issue completion open. |
| 2026-08-11 | 1.14.33 | fix(ground-roll, #4271): remediate independent review of the regional execution envelope by embedding and hashing the exact plan, enforcing fixed executor producer/version with variable source revision, binding ordered transition identities and coordinates to both ground events and real plan crossings, aligning Python wire safe-number/integral-number/nonblank-text/vector validation with TypeScript, rejecting same-surface and fabricated mappings or unsubstantiated null-result ledgers, replacing synthetic evidence with executor-produced representable/cancelled/failed fixtures, and adding a shared adversarial parity corpus; preserve frozen base-result v1 and retain UI, compiled physics, downstream parity, protected evidence, release, and issue completion as open work. |
diff --git a/docs/development/RATE_OF_CLOSURE_CAMPAIGN_HANDOFF.md b/docs/development/RATE_OF_CLOSURE_CAMPAIGN_HANDOFF.md
index d2b6cd851..2e395cdc8 100644
--- a/docs/development/RATE_OF_CLOSURE_CAMPAIGN_HANDOFF.md
+++ b/docs/development/RATE_OF_CLOSURE_CAMPAIGN_HANDOFF.md
@@ -1762,3 +1762,26 @@ production build, manifest and eight manifest tests, docs governance,
structural budgets, and diff checks. The inherited 500 kB build advisory
remains. Complete an independent audit before publication; then create a normal
stacked draft child without retargeting or rewriting PR #4350.
+
+## 2026-08-11 Complete regional result readback continuation
+
+Local branch `feat/4271-regional-result-readback` starts exactly at draft PR
+#4351 head `fe463b5503a8c7b599a329da18bb690d008871cd`. The next bounded result-
+usability slice makes matched PyQt6/React evidence inspection complete for the
+existing frozen result: carry, bounce-air, skid, roll, surface path, total,
+final downrange/offline, bounce count, ground time, completion, model and
+surface-provider identities/versions, calibration evidence, ordered observed
+phases, typed warnings, executor provenance, and qualification limits.
+
+Cancellation/failure cannot fabricate ground-only values, and partial results
+remain visibly censored. React still executes no ground physics. Trajectory and
+event tables, export/playback, executor input construction/invocation, measured
+calibration, compiled parity, Upstream consumers, protected evidence, release,
+and issue completion remain open.
+
+Exact local gates are green: 208 expanded Python ground/plan/PyQt/layout tests,
+111 React files / 691 tests, strict MyPy, Ruff/format, TypeScript type-check,
+zero-warning ESLint, the 202-module production build, campaign-manifest
+validation plus eight manifest tests, documentation governance, module-size
+budget, placeholder scan, and diff checks. The inherited 500 kB build advisory
+remains. Independent review is required before publication.
diff --git a/docs/release/rate_of_closure_campaign.v1.json b/docs/release/rate_of_closure_campaign.v1.json
index 121b79c0d..a707ddfb1 100644
--- a/docs/release/rate_of_closure_campaign.v1.json
+++ b/docs/release/rate_of_closure_campaign.v1.json
@@ -258,6 +258,16 @@
"state": "open",
"merge_commit_sha": null,
"protection_evidence": "not_recorded"
+ },
+ {
+ "id": "pr-4351",
+ "pr": 4351,
+ "branch": "feat/4271-regional-execution-ui",
+ "base_branch": "feat/4271-regional-execution-binding",
+ "head_sha": "fe463b5503a8c7b599a329da18bb690d008871cd",
+ "state": "open",
+ "merge_commit_sha": null,
+ "protection_evidence": "not_recorded"
}
],
"test_evidence": [
@@ -1262,7 +1272,8 @@
"pr-4335",
"pr-4339",
"pr-4342",
- "pr-4350"
+ "pr-4350",
+ "pr-4351"
],
"supported_surfaces": {
"tools.pyqt6": "implemented_unverified",
@@ -1279,12 +1290,12 @@
"ground-regional-ui-helper-local-d217",
"ground-regional-io-local-8e1c"
],
- "evidence_gap": "The contract, flight-transfer, impact/bounce, static-plane skid/roll, regional-surface, regional-plan wire, regional-editor, regional request-I/O, and regional execution carriers remain open on draft PRs 4285, 4288, 4302, 4304, 4332, 4335, 4339, 4342, and 4350. A local unpublished child adds matched PyQt6/React import-only readback of exact plan-bound Python execution evidence. Fresh protected evidence for the reconciled heads, UI executor invocation/playback, workspace model-input persistence, measured calibration, changing geometry, compiled-runtime physics, and downstream parity remain absent.",
+ "evidence_gap": "The contract, flight-transfer, impact/bounce, static-plane skid/roll, regional-surface, regional-plan wire, regional-editor, regional request-I/O, regional execution, and evidence-readback carriers remain open on draft PRs 4285, 4288, 4302, 4304, 4332, 4335, 4339, 4342, 4350, and 4351. A local unpublished child completes matched summary/result inspection, warnings, phase presence, calibration evidence, and provider identity without browser physics. Fresh protected evidence for the reconciled heads, UI executor invocation, trajectory/event tables, export/playback, workspace model-input persistence, measured calibration, changing geometry, compiled-runtime physics, and downstream parity remain absent.",
"limitations": [
"Airborne flight must first terminate against physical terrain and preserve terminal angular velocity.",
"Current UpstreamDrift terrain and putting implementations are reference inputs rather than the qualified authority.",
"The #4270 rigid impact law does not consume firmness, grass, compressibility, moisture, or rolling-resistance fields.",
- "Published open PR #4304 qualifies one immutable planar profile and optional finite tangent-axis edge; open PR #4332 adds bounded coplanar material overlays; open PR #4335 adds a strict Python/TypeScript plan wire boundary and Python resolver; open PR #4339 adds matched PyQt6/React request editors; open PR #4342 adds editor-qualified canonical request import/export with native atomic semantics and explicit browser limitations; and open PR #4350 adds UI-neutral Python execution plus an embedded-plan-bound result/provenance envelope with validated transition crossings. A local child adds matched import-only execution evidence readback. Changing normals, height or velocity discontinuities, terrain deformation, torsional-spin damping, roll-to-skid transitions, UI executor invocation/playback, measured calibration, workspace model-input persistence, compiled or TypeScript regional physics, and downstream adapters remain unimplemented."
+ "Published open PR #4304 qualifies one immutable planar profile and optional finite tangent-axis edge; open PR #4332 adds bounded coplanar material overlays; open PR #4335 adds a strict Python/TypeScript plan wire boundary and Python resolver; open PR #4339 adds matched PyQt6/React request editors; open PR #4342 adds editor-qualified canonical request import/export with native atomic semantics and explicit browser limitations; open PR #4350 adds UI-neutral Python execution plus an embedded-plan-bound result/provenance envelope with validated transition crossings; and open PR #4351 adds matched strict plan-bound evidence import/readback. A local child completes all frozen summary/result fields, warnings, calibration, provider identity, and observed-phase readback. Changing normals, height or velocity discontinuities, terrain deformation, torsional-spin damping, roll-to-skid transitions, UI executor invocation, trajectory/event tables, export/playback, measured calibration, workspace model-input persistence, compiled or TypeScript regional physics, and downstream adapters remain unimplemented."
],
"depends_on_issues": [
4191
diff --git a/src/rate_of_closure/AGENT_HANDOFF.md b/src/rate_of_closure/AGENT_HANDOFF.md
index 9597632a6..eccc2e835 100644
--- a/src/rate_of_closure/AGENT_HANDOFF.md
+++ b/src/rate_of_closure/AGENT_HANDOFF.md
@@ -1447,3 +1447,25 @@ remains. Independent review is still required before any GitHub write. Do not
claim #4267/#4271 complete; UI executor inputs/invocation, playback, measured
calibration, compiled regional physics, downstream parity, protected evidence,
and release remain open.
+
+## 2026-08-11 Complete regional result readback
+
+Local unpublished `feat/4271-regional-result-readback` is an exact child of PR
+#4351 head `fe463b5503a8c7b599a329da18bb690d008871cd`. Its matched PyQt6 and React
+evidence presenters now expose every qualified result summary field, final
+position, ground time, completion, bounce count, model/surface authority,
+calibration evidence, ordered phases, warnings, executor provenance, and
+limitations. Null-result states remain unavailable rather than fabricated;
+partial endpoints retain their warning.
+
+This remains import/readback only. Complete local gates and independent review
+before any GitHub write. Executor invocation, trajectory/event tables, export,
+playback, calibration workflows, compiled parity, downstream parity, protected
+evidence, release, and #4267/#4271 completion remain open.
+
+Exact local gates are green: 208 expanded Python ground/plan/PyQt/layout tests,
+111 React files / 691 tests, strict MyPy, Ruff/format, TypeScript type-check,
+zero-warning ESLint, the 202-module production build, campaign-manifest
+validation plus eight manifest tests, documentation governance, module-size
+budget, placeholder scan, and diff checks. The inherited 500 kB build advisory
+remains.
diff --git a/src/rate_of_closure/application/regional_execution_readback.py b/src/rate_of_closure/application/regional_execution_readback.py
index 4265cda45..c9eb74e20 100644
--- a/src/rate_of_closure/application/regional_execution_readback.py
+++ b/src/rate_of_closure/application/regional_execution_readback.py
@@ -15,6 +15,15 @@
from .bounded_text_files import read_bounded_utf8
+@dataclass(frozen=True)
+class RegionalExecutionWarningReadback:
+ """Typed warning projection retained for matched client presentation."""
+
+ code: str
+ severity: str
+ message: str
+
+
@dataclass(frozen=True)
class RegionalExecutionReadback:
"""Small UI-neutral projection of one frozen execution envelope."""
@@ -23,13 +32,29 @@ class RegionalExecutionReadback:
failure_reason: str | None
plan_id: str
surface_id: str
+ surface_provider_id: str
+ surface_provider_version: str
model_id: str
model_version: str
termination_reason: str | None
+ ground_time_s: float | None
+ completed: bool | None
transition_count: int
+ carry_distance_m: float | None
+ bounce_air_distance_m: float | None
skid_distance_m: float | None
roll_distance_m: float | None
+ surface_path_distance_m: float | None
total_distance_m: float | None
+ final_downrange_m: float | None
+ final_offline_m: float | None
+ bounce_count: int | None
+ calibration_id: str | None
+ calibration_kind: str | None
+ calibration_source: str | None
+ calibration_confidence: float | None
+ observed_phases: tuple[str, ...]
+ warnings: tuple[RegionalExecutionWarningReadback, ...]
executor_source_revision: str
executor_input_sha256: str
limitations: tuple[str, ...]
@@ -58,6 +83,23 @@ def regional_execution_readback(
raise ValueError("execution evidence does not match the current regional plan")
ground = result.ground_result
summary = None if ground is None else ground.summary
+ phases = (
+ ()
+ if ground is None
+ else tuple(dict.fromkeys(point.phase.value for point in ground.trajectory))
+ )
+ warnings = (
+ ()
+ if ground is None
+ else tuple(
+ RegionalExecutionWarningReadback(
+ code=item.code,
+ severity=item.severity.value,
+ message=item.message,
+ )
+ for item in ground.warnings
+ )
+ )
return RegionalExecutionReadback(
status=result.status.value,
failure_reason=None
@@ -65,13 +107,35 @@ def regional_execution_readback(
else result.failure_reason.value,
plan_id=result.plan_id,
surface_id=result.surface_id,
+ surface_provider_id=result.regional_plan.base_surface.provider_id,
+ surface_provider_version=result.regional_plan.base_surface.provider_version,
model_id=result.model_id,
model_version=result.model_version,
termination_reason=None if ground is None else ground.termination.reason.value,
+ ground_time_s=None if ground is None else ground.termination.time_s,
+ completed=None if ground is None else ground.termination.completed,
transition_count=len(result.transitions),
+ carry_distance_m=None if summary is None else summary.carry_distance_m,
+ bounce_air_distance_m=(
+ None if summary is None else summary.bounce_air_distance_m
+ ),
skid_distance_m=None if summary is None else summary.skid_distance_m,
roll_distance_m=None if summary is None else summary.roll_distance_m,
+ surface_path_distance_m=(
+ None if summary is None else summary.surface_path_distance_m
+ ),
total_distance_m=None if summary is None else summary.total_distance_m,
+ final_downrange_m=None if summary is None else summary.final_downrange_m,
+ final_offline_m=None if summary is None else summary.final_offline_m,
+ bounce_count=None if summary is None else summary.bounce_count,
+ calibration_id=None if ground is None else ground.calibration.calibration_id,
+ calibration_kind=None if ground is None else ground.calibration.kind.value,
+ calibration_source=None if ground is None else ground.calibration.source,
+ calibration_confidence=(
+ None if ground is None else ground.calibration.confidence
+ ),
+ observed_phases=phases,
+ warnings=warnings,
executor_source_revision=result.executor_provenance.source_revision,
executor_input_sha256=result.executor_provenance.input_sha256,
limitations=result.limitations,
@@ -101,6 +165,7 @@ def read_regional_execution_evidence(
__all__ = [
"RegionalExecutionEvidence",
"RegionalExecutionReadback",
+ "RegionalExecutionWarningReadback",
"read_regional_execution_evidence",
"regional_execution_readback",
]
diff --git a/src/rate_of_closure/ui/pyqt6/regional_execution_evidence.py b/src/rate_of_closure/ui/pyqt6/regional_execution_evidence.py
index 5a6df23b9..40936431f 100644
--- a/src/rate_of_closure/ui/pyqt6/regional_execution_evidence.py
+++ b/src/rate_of_closure/ui/pyqt6/regional_execution_evidence.py
@@ -10,6 +10,7 @@
QFormLayout,
QGroupBox,
QLabel,
+ QPlainTextEdit,
QPushButton,
QWidget,
)
@@ -49,8 +50,9 @@ def __init__(self, host: RegionalExecutionEvidenceHost, parent: QWidget) -> None
self.status_label = QLabel("No execution evidence loaded.")
self.status_label.setWordWrap(True)
self.status_label.setAccessibleName("Regional execution evidence status")
- self.readback_label = QLabel("No accepted evidence")
- self.readback_label.setWordWrap(True)
+ self.readback_label = QPlainTextEdit("No accepted evidence")
+ self.readback_label.setReadOnly(True)
+ self.readback_label.setMinimumHeight(180)
self.readback_label.setAccessibleName("Regional execution evidence readback")
layout = QFormLayout(self)
layout.addRow(self.description)
@@ -60,7 +62,7 @@ def __init__(self, host: RegionalExecutionEvidenceHost, parent: QWidget) -> None
def clear(self) -> None:
"""Remove evidence made stale by a visible plan edit."""
- self.readback_label.setText("No accepted evidence")
+ self.readback_label.setPlainText("No accepted evidence")
self.status_label.setText("Plan changed; execution evidence must be reloaded.")
def open(self) -> None:
@@ -85,7 +87,7 @@ def open(self) -> None:
self.status_label.setAccessibleName("Regional execution evidence error")
return
self.recent_path = path
- self.readback_label.setText(_format_readback(evidence.readback))
+ self.readback_label.setPlainText(_format_readback(evidence.readback))
self.status_label.setText(f"Loaded {path.name}. No physics executed.")
self.status_label.setAccessibleName("Regional execution evidence success")
@@ -96,16 +98,52 @@ def _metric(value: float | None) -> str:
def _format_readback(value: RegionalExecutionReadback) -> str:
terminal = value.termination_reason or value.failure_reason or "unavailable"
- return (
- f"{value.status} · {terminal} · plan {value.plan_id} · surface "
- f"{value.surface_id} · model {value.model_id} {value.model_version} · "
+ lines = [
+ f"Status: {value.status} · termination: {terminal} · "
+ f"ground time: {_seconds(value.ground_time_s)}",
+ f"Plan: {value.plan_id} · surface: {value.surface_id} · "
+ f"provider: {value.surface_provider_id} {value.surface_provider_version} · "
+ f"model: {value.model_id} {value.model_version}",
+ f"Carry {_metric(value.carry_distance_m)} · "
+ f"bounce {_metric(value.bounce_air_distance_m)} · "
f"skid {_metric(value.skid_distance_m)} · "
- f"roll {_metric(value.roll_distance_m)} · "
+ f"roll {_metric(value.roll_distance_m)}",
+ f"Surface path {_metric(value.surface_path_distance_m)} · "
f"total {_metric(value.total_distance_m)} · "
- f"{value.transition_count} transition(s) · "
- f"source {value.executor_source_revision} · "
- f"input {value.executor_input_sha256} · "
- f"limits {', '.join(value.limitations)}"
+ f"final downrange {_metric(value.final_downrange_m)} · "
+ f"final offline {_metric(value.final_offline_m)}",
+ f"Bounces: {_count(value.bounce_count)} · transitions: "
+ f"{value.transition_count} · phases: {_phases(value.observed_phases)}",
+ f"Calibration: {_calibration(value)}",
+ f"Executor source: {value.executor_source_revision} · "
+ f"input: {value.executor_input_sha256}",
+ f"Qualification limits: {', '.join(value.limitations)}",
+ ]
+ lines.extend(
+ f"Warning {item.code} [{item.severity}]: {item.message}"
+ for item in value.warnings
+ )
+ return "\n".join(lines)
+
+
+def _seconds(value: float | None) -> str:
+ return "unavailable" if value is None else f"{value:.3f} s"
+
+
+def _count(value: int | None) -> str:
+ return "unavailable" if value is None else str(value)
+
+
+def _phases(values: tuple[str, ...]) -> str:
+ return "unavailable" if not values else " -> ".join(values)
+
+
+def _calibration(value: RegionalExecutionReadback) -> str:
+ if value.calibration_kind is None:
+ return "unavailable"
+ return (
+ f"{value.calibration_kind} · {value.calibration_id} · "
+ f"{value.calibration_source} · confidence {value.calibration_confidence}"
)
diff --git a/src/rate_of_closure/web/src/components/RegionalExecutionEvidencePanel.test.tsx b/src/rate_of_closure/web/src/components/RegionalExecutionEvidencePanel.test.tsx
index 13ad2a00b..2220f8c0b 100644
--- a/src/rate_of_closure/web/src/components/RegionalExecutionEvidencePanel.test.tsx
+++ b/src/rate_of_closure/web/src/components/RegionalExecutionEvidencePanel.test.tsx
@@ -24,7 +24,11 @@ describe("RegionalExecutionEvidencePanel", () => {
"Regional execution evidence readback",
)).toBeVisible());
expect(screen.getByText("partial")).toBeVisible();
- expect(screen.getByText("0.254 m")).toBeVisible();
+ expect(screen.getAllByText("0.254 m")).toHaveLength(2);
+ expect(screen.getByText("0.040 m")).toBeVisible();
+ expect(screen.getByText("1.155 s")).toBeVisible();
+ expect(screen.getByText("impact → skid → roll")).toBeVisible();
+ expect(screen.getByText(/CENSORED_ENDPOINT/)).toBeVisible();
expect(screen.getByText(/no browser physics executed/i)).toBeVisible();
fireEvent.change(input!, { target: { files: [{
@@ -33,7 +37,7 @@ describe("RegionalExecutionEvidencePanel", () => {
text: async () => "{}",
}] } });
await waitFor(() => expect(screen.getByRole("alert")).toBeVisible());
- expect(screen.getByText("0.254 m")).toBeVisible();
+ expect(screen.getAllByText("0.254 m")).toHaveLength(2);
expect(screen.getByText(/prior accepted execution evidence was preserved/i)).toBeVisible();
});
});
diff --git a/src/rate_of_closure/web/src/components/RegionalExecutionEvidencePanel.tsx b/src/rate_of_closure/web/src/components/RegionalExecutionEvidencePanel.tsx
index 868af08bb..98ef1b4a5 100644
--- a/src/rate_of_closure/web/src/components/RegionalExecutionEvidencePanel.tsx
+++ b/src/rate_of_closure/web/src/components/RegionalExecutionEvidencePanel.tsx
@@ -1,4 +1,4 @@
-import { type ChangeEvent, useRef, useState } from "react";
+import { type ChangeEvent, type ReactNode, useRef, useState } from "react";
import type { GroundRegionalMaterialPlanRequest } from "../model/groundRegionalPlan";
import {
@@ -8,6 +8,12 @@ import {
const metric = (value: number | null): string =>
value === null ? "Unavailable" : `${value.toFixed(3)} m`;
+const seconds = (value: number | null): string =>
+ value === null ? "Unavailable" : `${value.toFixed(3)} s`;
+
+function ReadbackItem(props: { readonly label: string; readonly children: ReactNode }) {
+ return {props.label}{props.children}
;
+}
export function RegionalExecutionEvidencePanel(props: {
readonly currentPlan: () => GroundRegionalMaterialPlanRequest;
@@ -52,14 +58,44 @@ export function RegionalExecutionEvidencePanel(props: {
className="mt-3 text-xs text-slate-400">{status}
{readback !== null &&
- - Status
- {readback.status}
- - Termination
- {readback.terminationReason ?? readback.failureReason ?? "Unavailable"}
- - Plan / surface
- {readback.planId} / {readback.surfaceId}
- - Model
- {readback.modelId} {readback.modelVersion}
- - Skid
- {metric(readback.skidDistanceM)}
- - Roll
- {metric(readback.rollDistanceM)}
- - Total
- {metric(readback.totalDistanceM)}
- - Surface transitions
- {readback.transitionCount}
+ {readback.status}
+
+ {readback.terminationReason ?? readback.failureReason ?? "Unavailable"}
+
+ {seconds(readback.groundTimeS)}
+
+ {readback.completed === null ? "Unavailable" : readback.completed
+ ? "Completed" : "Observed endpoint"}
+
+
+ {readback.planId} / {readback.surfaceId}
+
+
+ {readback.surfaceProviderId} {readback.surfaceProviderVersion}
+
+
+ {readback.modelId} {readback.modelVersion}
+
+ {metric(readback.carryDistanceM)}
+ {metric(readback.bounceAirDistanceM)}
+ {metric(readback.skidDistanceM)}
+ {metric(readback.rollDistanceM)}
+ {metric(readback.surfacePathDistanceM)}
+ {metric(readback.totalDistanceM)}
+ {metric(readback.finalDownrangeM)}
+ {metric(readback.finalOfflineM)}
+ {readback.bounceCount ?? "Unavailable"}
+ {readback.transitionCount}
+
+ {readback.calibrationKind === null ? "Unavailable"
+ : `${readback.calibrationKind} · ${readback.calibrationId} · ` +
+ `${readback.calibrationSource} · confidence ` +
+ `${readback.calibrationConfidence}`}
+
+
+ {readback.observedPhases.length === 0
+ ? "Unavailable" : readback.observedPhases.join(" → ")}
+
- Executor provenance
- {readback.executorSourceRevision} · {readback.executorInputSha256}
@@ -68,6 +104,14 @@ export function RegionalExecutionEvidencePanel(props: {
- Qualification limits
- {readback.limitations.join(" · ")}
+ {readback.warnings.length > 0 &&
+
- Warnings
+
+ {readback.warnings.map((warning, index) => -
+ {warning.code} [{warning.severity}] — {warning.message}
+
)}
+
+
}
}
);
diff --git a/src/rate_of_closure/web/src/model/regionalExecutionReadback.test.ts b/src/rate_of_closure/web/src/model/regionalExecutionReadback.test.ts
index 83c85dd2e..12797d0a2 100644
--- a/src/rate_of_closure/web/src/model/regionalExecutionReadback.test.ts
+++ b/src/rate_of_closure/web/src/model/regionalExecutionReadback.test.ts
@@ -16,10 +16,47 @@ describe("regional execution evidence readback", () => {
expect(readback.status).toBe("partial");
expect(readback.planId).toBe("regional-execution-plan-001");
expect(readback.terminationReason).toBe("time_limit");
+ expect(readback.groundTimeS).toBeCloseTo(1.155);
+ expect(readback.completed).toBe(false);
expect(readback.transitionCount).toBe(1);
+ expect(readback.carryDistanceM).toBeCloseTo(0);
+ expect(readback.bounceAirDistanceM).toBeCloseTo(0.04);
expect(readback.skidDistanceM).toBeCloseTo(0);
expect(readback.rollDistanceM).toBeCloseTo(0.25374857896);
+ expect(readback.surfacePathDistanceM).toBeCloseTo(0.25374857896);
expect(readback.totalDistanceM).toBeCloseTo(0.2937485791);
+ expect(readback.finalDownrangeM).toBeCloseTo(0.2937485791);
+ expect(readback.finalOfflineM).toBeCloseTo(0);
+ expect(readback.bounceCount).toBe(1);
+ expect(readback.surfaceProviderId).toBe("tools.planar-surface");
+ expect(readback.surfaceProviderVersion).toBe("1.0.0");
+ expect(readback.calibrationId).toBe("literature-default-2026-08");
+ expect(readback.calibrationKind).toBe("literature");
+ expect(readback.calibrationSource).toBe("documented literature basis");
+ expect(readback.calibrationConfidence).toBeCloseTo(0.6);
+ expect(readback.observedPhases).toEqual(["impact", "skid", "roll"]);
+ expect(readback.warnings[readback.warnings.length - 1]).toEqual({
+ code: "CENSORED_ENDPOINT",
+ severity: "warning",
+ message: "Distance totals describe only the observed endpoint and are not projected final-rest metrics.",
+ });
+ });
+
+ it("does not fabricate metrics for null-result cancellation evidence", () => {
+ const cancelled = parseGroundRegionalExecutionResult(fixture.cancelled.result);
+ const readback = regionalExecutionReadback(cancelled, cancelled.regional_plan);
+
+ expect(readback.failureReason).toBe("cancelled");
+ expect(readback.groundTimeS).toBeNull();
+ expect(readback.completed).toBeNull();
+ expect(readback.carryDistanceM).toBeNull();
+ expect(readback.bounceCount).toBeNull();
+ expect(readback.calibrationKind).toBeNull();
+ expect(readback.calibrationId).toBeNull();
+ expect(readback.calibrationSource).toBeNull();
+ expect(readback.calibrationConfidence).toBeNull();
+ expect(readback.observedPhases).toEqual([]);
+ expect(readback.warnings).toEqual([]);
});
it("rejects evidence for a different plan and oversize browser files", async () => {
diff --git a/src/rate_of_closure/web/src/model/regionalExecutionReadback.ts b/src/rate_of_closure/web/src/model/regionalExecutionReadback.ts
index 4a8fb1586..4adb59041 100644
--- a/src/rate_of_closure/web/src/model/regionalExecutionReadback.ts
+++ b/src/rate_of_closure/web/src/model/regionalExecutionReadback.ts
@@ -13,18 +13,40 @@ export interface RegionalExecutionReadback {
readonly failureReason: string | null;
readonly planId: string;
readonly surfaceId: string;
+ readonly surfaceProviderId: string;
+ readonly surfaceProviderVersion: string;
readonly modelId: string;
readonly modelVersion: string;
readonly terminationReason: string | null;
+ readonly groundTimeS: number | null;
+ readonly completed: boolean | null;
readonly transitionCount: number;
+ readonly carryDistanceM: number | null;
+ readonly bounceAirDistanceM: number | null;
readonly skidDistanceM: number | null;
readonly rollDistanceM: number | null;
+ readonly surfacePathDistanceM: number | null;
readonly totalDistanceM: number | null;
+ readonly finalDownrangeM: number | null;
+ readonly finalOfflineM: number | null;
+ readonly bounceCount: number | null;
+ readonly calibrationId: string | null;
+ readonly calibrationKind: string | null;
+ readonly calibrationSource: string | null;
+ readonly calibrationConfidence: number | null;
+ readonly observedPhases: readonly string[];
+ readonly warnings: readonly RegionalExecutionWarningReadback[];
readonly executorSourceRevision: string;
readonly executorInputSha256: string;
readonly limitations: readonly string[];
}
+export interface RegionalExecutionWarningReadback {
+ readonly code: string;
+ readonly severity: string;
+ readonly message: string;
+}
+
export interface RegionalExecutionEvidence {
readonly result: GroundRegionalExecutionResult;
readonly readback: RegionalExecutionReadback;
@@ -45,18 +67,43 @@ export const regionalExecutionReadback = (
}
const ground = result.ground_result;
const summary = ground?.summary ?? null;
+ const phases = Object.freeze(Array.from(new Set(
+ ground?.trajectory.map((point) => point.phase) ?? [],
+ )));
+ const warnings = Object.freeze((ground?.warnings ?? []).map((warning) =>
+ Object.freeze({
+ code: warning.code,
+ severity: warning.severity,
+ message: warning.message,
+ })));
return Object.freeze({
status: result.status,
failureReason: result.failure_reason,
planId: result.plan_id,
surfaceId: result.surface_id,
+ surfaceProviderId: result.regional_plan.base_surface.provider_id,
+ surfaceProviderVersion: result.regional_plan.base_surface.provider_version,
modelId: result.model_id,
modelVersion: result.model_version,
terminationReason: ground?.termination.reason ?? null,
+ groundTimeS: ground?.termination.time_s ?? null,
+ completed: ground?.termination.completed ?? null,
transitionCount: result.transitions.length,
+ carryDistanceM: summary?.carry_distance_m ?? null,
+ bounceAirDistanceM: summary?.bounce_air_distance_m ?? null,
skidDistanceM: summary?.skid_distance_m ?? null,
rollDistanceM: summary?.roll_distance_m ?? null,
+ surfacePathDistanceM: summary?.surface_path_distance_m ?? null,
totalDistanceM: summary?.total_distance_m ?? null,
+ finalDownrangeM: summary?.final_downrange_m ?? null,
+ finalOfflineM: summary?.final_offline_m ?? null,
+ bounceCount: summary?.bounce_count ?? null,
+ calibrationId: ground?.calibration.calibration_id ?? null,
+ calibrationKind: ground?.calibration.kind ?? null,
+ calibrationSource: ground?.calibration.source ?? null,
+ calibrationConfidence: ground?.calibration.confidence ?? null,
+ observedPhases: phases,
+ warnings,
executorSourceRevision: result.executor_provenance.source_revision,
executorInputSha256: result.executor_provenance.input_sha256,
limitations: result.limitations,
diff --git a/tests/rate_of_closure/test_regional_execution_readback.py b/tests/rate_of_closure/test_regional_execution_readback.py
index 4072af93d..0c4e387d9 100644
--- a/tests/rate_of_closure/test_regional_execution_readback.py
+++ b/tests/rate_of_closure/test_regional_execution_readback.py
@@ -39,14 +39,51 @@ def test_readback_reports_frozen_executor_evidence_without_running_physics() ->
assert readback.status == "partial"
assert readback.plan_id == "regional-execution-plan-001"
assert readback.surface_id == "firm-fairway"
+ assert readback.surface_provider_id == "tools.planar-surface"
+ assert readback.surface_provider_version == "1.0.0"
assert readback.termination_reason == "time_limit"
+ assert readback.ground_time_s == pytest.approx(1.155)
+ assert readback.completed is False
assert readback.transition_count == 1
+ assert readback.carry_distance_m == pytest.approx(0.0)
+ assert readback.bounce_air_distance_m == pytest.approx(0.04)
assert readback.skid_distance_m == pytest.approx(0.0)
assert readback.roll_distance_m == pytest.approx(0.25374857896)
+ assert readback.surface_path_distance_m == pytest.approx(0.25374857896)
assert readback.total_distance_m == pytest.approx(0.2937485791)
+ assert readback.final_downrange_m == pytest.approx(0.2937485791)
+ assert readback.final_offline_m == pytest.approx(0.0)
+ assert readback.bounce_count == 1
+ assert readback.calibration_kind == "literature"
+ assert readback.calibration_id == "literature-default-2026-08"
+ assert readback.calibration_source == "documented literature basis"
+ assert readback.calibration_confidence == pytest.approx(0.6)
+ assert readback.observed_phases == ("impact", "skid", "roll")
+ assert len(readback.warnings) == 4
+ assert readback.warnings[-1].code == "CENSORED_ENDPOINT"
+ assert readback.warnings[-1].severity == "warning"
assert readback.executor_source_revision == "ground-regional-execution-v1"
+def test_null_result_readback_does_not_fabricate_ground_metrics() -> None:
+ result = _result("cancelled")
+
+ readback = regional_execution_readback(result, result.regional_plan)
+
+ assert readback.status == "cancelled"
+ assert readback.failure_reason == "cancelled"
+ assert readback.ground_time_s is None
+ assert readback.completed is None
+ assert readback.carry_distance_m is None
+ assert readback.bounce_count is None
+ assert readback.calibration_kind is None
+ assert readback.calibration_id is None
+ assert readback.calibration_source is None
+ assert readback.calibration_confidence is None
+ assert readback.observed_phases == ()
+ assert readback.warnings == ()
+
+
def test_readback_rejects_evidence_for_a_different_visible_plan() -> None:
result = _result()
different = replace(result.regional_plan, request_id="different-plan")
@@ -104,12 +141,15 @@ def current_request(self): # type: ignore[no-untyped-def]
box.open_button.click()
- assert "partial" in box.readback_label.text()
+ assert "partial" in box.readback_label.toPlainText()
+ assert "bounce 0.040 m" in box.readback_label.toPlainText()
+ assert "final offline 0.000 m" in box.readback_label.toPlainText()
+ assert "CENSORED_ENDPOINT" in box.readback_label.toPlainText()
assert "No physics executed" in box.status_label.text()
- accepted = box.readback_label.text()
+ accepted = box.readback_label.toPlainText()
target.write_text('{"request_id":"one","request_id":"two"}', encoding="utf-8")
box.open_button.click()
- assert box.readback_label.text() == accepted
+ assert box.readback_label.toPlainText() == accepted
assert "Prior accepted evidence was preserved" in box.status_label.text()
box.clear()
- assert box.readback_label.text() == "No accepted evidence"
+ assert box.readback_label.toPlainText() == "No accepted evidence"