Skip to content

feat(harness-e2e): collect lifecycle evidence - #773

Open
ytallo wants to merge 1 commit into
mainfrom
feat/harness-e2e-lifecycle-evidence
Open

feat(harness-e2e): collect lifecycle evidence#773
ytallo wants to merge 1 commit into
mainfrom
feat/harness-e2e-lifecycle-evidence

Conversation

@ytallo

@ytallo ytallo commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • preserve the terminal root StatusReport and collect the final session tree
  • collect status and transcripts for every session owned by the root
  • normalize lifecycle, timeline, effective function calls, and call/result integrity into HarnessExecutionEvidence
  • add common structural gates for tree completeness, session ownership, and call/result integrity
  • persist harness evidence and teardown removal details in results.json
  • keep the existing root transcript and scenario evaluators compatible

Error semantics

Failures to collect the tree, statuses, or transcripts remain infrastructure errors in the collect phase. Structural gates only fail when evidence was successfully collected and violates the lifecycle contract.

Validation

  • cargo fmt --all
  • cargo test -p harness-e2e — 102 passed

Summary by CodeRabbit

  • New Features

    • Added detailed execution evidence for end-to-end test runs, including session timelines, function calls, results, and lifecycle events.
    • Reports now include session trees, transcripts, terminal statuses, structural validation results, and cleanup details.
    • Added visibility into teardown attempts, removed resources, and cleanup errors.
  • Bug Fixes

    • Improved validation for incomplete sessions, missing statuses, and unmatched or duplicate function results.
    • Preserved partial test observations when failures occur.

@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview Aug 11, 2026 6:14pm
workers-tech-spec Ready Ready Preview Aug 11, 2026 6:14pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The E2E harness now collects session-tree data, transcripts, and statuses; builds execution evidence; evaluates structural gates; preserves partial observations; and records teardown outcomes in run reports.

Changes

E2E execution evidence

Layer / File(s) Summary
Evidence models and transcript normalization
harness/tests/e2e/src/evidence.rs
Adds serializable lifecycle, function-call, result, and timeline evidence. Normalizes transcripts and pairs function calls with results.
Structural validation
harness/tests/e2e/src/evidence.rs
Adds gates for tree completion, session ownership, parent relationships, terminal statuses, and call-result integrity.
Session-tree collection and observation data
harness/tests/e2e/src/context.rs, harness/tests/e2e/src/scenarios/mod.rs, harness/tests/e2e/src/main.rs
Adds helpers to retrieve tree-node statuses and transcripts. Stores terminal status, tree data, transcripts, and evidence in scenario observations.
Suite reporting and cleanup
harness/tests/e2e/src/suite.rs, harness/tests/e2e/src/report.rs
Builds evidence during execution, merges structural gates, preserves partial observations, and records teardown attempts, removals, and errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant E2eContext
  participant HarnessAPI
  participant EvidenceBuilder
  participant GateEvaluator
  participant E2eRunReport
  E2eContext->>HarnessAPI: retrieve session tree, statuses, and transcripts
  E2eContext-->>EvidenceBuilder: provide collected session data
  EvidenceBuilder->>EvidenceBuilder: build execution evidence
  EvidenceBuilder-->>GateEvaluator: provide evidence and session tree
  GateEvaluator->>GateEvaluator: evaluate structural gates
  GateEvaluator-->>E2eRunReport: add gates and harness evidence
Loading

Possibly related PRs

  • iii-hq/workers#644: Both changes modify the E2E harness run_suite flow and extend scenario reporting data.

Suggested reviewers: andersonleal

Poem

I hop through the session tree,
Gathering transcripts carefully.
Calls and results align in flight,
Gates check every branch is right.
Cleanup marks the trail I made—
Evidence safely displayed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: collecting lifecycle evidence in harness-e2e.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/harness-e2e-lifecycle-evidence
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harness-e2e-lifecycle-evidence

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (6)
harness/tests/e2e/src/evidence.rs (6)

295-301: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Include the offending call identifiers in the gate reason.

The failure message reports only counts. When this gate fails in CI, the reader cannot identify which calls lack results without opening the full evidence payload. Add a bounded sample of (session_id, call_id) pairs to the reason string.

♻️ Proposed refactor sketch
-    if bad > 0 || orphaned > 0 {
-        Some(format!(
-            "{bad} call(s) have missing or duplicate results; {orphaned} result(s) are orphaned"
-        ))
-    } else {
-        None
-    }
+    if bad > 0 || orphaned > 0 {
+        let sample = evidence
+            .calls
+            .iter()
+            .filter(|call| {
+                !matches!(
+                    call.result,
+                    FunctionResultEvidence::Succeeded { .. } | FunctionResultEvidence::Failed { .. }
+                )
+            })
+            .take(5)
+            .map(|call| {
+                format!(
+                    "{}:{}",
+                    call.session_id,
+                    call.call_id.as_deref().unwrap_or("<no-call-id>")
+                )
+            })
+            .collect::<Vec<_>>()
+            .join(", ");
+        Some(format!(
+            "{bad} call(s) have missing or duplicate results; {orphaned} result(s) are orphaned; sample=[{sample}]"
+        ))
+    } else {
+        None
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/tests/e2e/src/evidence.rs` around lines 295 - 301, Update the
gate-reason construction in the bad/orphaned evidence check to include a bounded
sample of offending (session_id, call_id) pairs alongside the existing counts.
Reuse the available evidence data and keep the output concise, ensuring the
reason still reports counts and remains unchanged when no failures exist.

156-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse evidence.complete for the tree-completeness gate.

build_evidence already computes tree.complete && all sessions terminal into evidence.complete. The gate recomputes the same expression. If one definition changes later, the two can diverge.

♻️ Proposed refactor
-    let tree_complete =
-        metrics.complete && tree.complete && evidence.sessions.iter().all(|s| s.terminal);
+    let tree_complete = metrics.complete && evidence.complete;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/tests/e2e/src/evidence.rs` around lines 156 - 157, Update the
tree-completeness gate in the surrounding evidence evaluation to reuse
evidence.complete instead of recomputing tree.complete and session terminality.
Preserve the existing metrics.complete requirement while relying on the
canonical value produced by build_evidence.

304-360: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Silent fallback when the transcript shape does not match.

Line 311 requires messages.messages to be a JSON array. If the transcript payload uses a different envelope, entries is empty, no calls or timeline events are produced, and every structural gate passes vacuously. The harness then reports success with no evidence.

Consider recording a per-session parse indicator in the evidence so a shape change surfaces as a gate failure rather than as empty evidence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/tests/e2e/src/evidence.rs` around lines 304 - 360, Update
normalize_transcripts to record whether each session contains the expected
messages.messages JSON array, and propagate that parse indicator through the
evidence result. Ensure structural validation treats a session with a missing or
differently shaped envelope as a gate failure instead of accepting empty calls
and timeline evidence.

430-437: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The _nudge_ substring match is a fragile classifier.

Line 433 classifies an entry as ValidationNudge when its entry_id contains _nudge_. An entry id that contains that substring for an unrelated reason is misclassified. The origin.validation check on Line 434 is the reliable signal.

If the producer always sets origin.validation for nudges, remove the substring heuristic. If it does not, add a comment that names the producer that generates the _nudge_ id format.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/tests/e2e/src/evidence.rs` around lines 430 - 437, Update the
`TimelineEventKind` classification condition to rely on `origin.validation ==
Some(true)` and remove the fragile `entry_id` `_nudge_` substring check if
producers consistently set that field; otherwise retain the heuristic only with
a comment identifying the producer responsible for the `_nudge_` ID format.

482-538: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for the structural gate functions.

The tests cover normalize_transcripts only. ownership_error, call_integrity_error, and build_evidence carry the gate logic and have no coverage. Uncovered branches include the duplicate-session-id check, the depth mismatch check, the cycle check, and the orphaned-result count.

I can generate table-driven tests for these functions. Do you want me to open an issue to track this?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/tests/e2e/src/evidence.rs` around lines 482 - 538, Extend the
existing tests module with focused tests for ownership_error,
call_integrity_error, and build_evidence. Cover duplicate session IDs, depth
mismatches, cyclic parent relationships, and orphaned-result counts, asserting
each gate reports the expected error or evidence outcome; use table-driven cases
where practical.

203-205: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Derive the status string from TurnStatus serialization.

AwaitingFunctions serializes as awaiting_functions, but the current Debug conversion produces awaitingfunctions. Use the serialized value in status_name before writing the evidence to results.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/tests/e2e/src/evidence.rs` around lines 203 - 205, Update status_name
to derive the status string from TurnStatus serialization rather than the Debug
representation, preserving the expected snake_case value such as
awaiting_functions before evidence is written to results.json.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@harness/tests/e2e/src/evidence.rs`:
- Around line 461-478: Update classify_entry to emit a timeline event for every
assistant content block whose type is "function_call" instead of selecting only
the first via find. Adjust the surrounding timeline construction to increment
the sequence per emitted call, preserving each block’s call_id and function_id
so parallel calls are all represented in results.json.

In `@harness/tests/e2e/src/suite.rs`:
- Around line 444-485: Update the collection flow around session_tree,
session_transcripts, and statuses so each successful checkpoint is persisted:
assign report.metrics immediately after metrics collection, extract the root
transcript from session_transcripts and assign report.transcript before
requesting statuses, and reuse that transcript for ScenarioObservation. Keep
report.harness_evidence assigned only after build_evidence completes
successfully.

---

Nitpick comments:
In `@harness/tests/e2e/src/evidence.rs`:
- Around line 295-301: Update the gate-reason construction in the bad/orphaned
evidence check to include a bounded sample of offending (session_id, call_id)
pairs alongside the existing counts. Reuse the available evidence data and keep
the output concise, ensuring the reason still reports counts and remains
unchanged when no failures exist.
- Around line 156-157: Update the tree-completeness gate in the surrounding
evidence evaluation to reuse evidence.complete instead of recomputing
tree.complete and session terminality. Preserve the existing metrics.complete
requirement while relying on the canonical value produced by build_evidence.
- Around line 304-360: Update normalize_transcripts to record whether each
session contains the expected messages.messages JSON array, and propagate that
parse indicator through the evidence result. Ensure structural validation treats
a session with a missing or differently shaped envelope as a gate failure
instead of accepting empty calls and timeline evidence.
- Around line 430-437: Update the `TimelineEventKind` classification condition
to rely on `origin.validation == Some(true)` and remove the fragile `entry_id`
`_nudge_` substring check if producers consistently set that field; otherwise
retain the heuristic only with a comment identifying the producer responsible
for the `_nudge_` ID format.
- Around line 482-538: Extend the existing tests module with focused tests for
ownership_error, call_integrity_error, and build_evidence. Cover duplicate
session IDs, depth mismatches, cyclic parent relationships, and orphaned-result
counts, asserting each gate reports the expected error or evidence outcome; use
table-driven cases where practical.
- Around line 203-205: Update status_name to derive the status string from
TurnStatus serialization rather than the Debug representation, preserving the
expected snake_case value such as awaiting_functions before evidence is written
to results.json.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4fa64ff9-d959-4bf6-9c65-18bafc6f9d2b

📥 Commits

Reviewing files that changed from the base of the PR and between 50b37eb and 6949753.

📒 Files selected for processing (6)
  • harness/tests/e2e/src/context.rs
  • harness/tests/e2e/src/evidence.rs
  • harness/tests/e2e/src/main.rs
  • harness/tests/e2e/src/report.rs
  • harness/tests/e2e/src/scenarios/mod.rs
  • harness/tests/e2e/src/suite.rs

Comment on lines +461 to +478
if message.get("role").and_then(Value::as_str) == Some("assistant") {
if let Some(block) = message
.get("content")
.and_then(Value::as_array)
.into_iter()
.flatten()
.find(|b| b.get("type").and_then(Value::as_str) == Some("function_call"))
{
return TimelineEventKind::FunctionCall {
call_id: block.get("id").and_then(Value::as_str).map(str::to_owned),
function_id: block
.get("function_id")
.and_then(Value::as_str)
.unwrap_or("unknown")
.into(),
};
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

The timeline records only the first function call in a multi-call assistant entry.

classify_entry returns one TimelineEventKind per entry and uses find to select the first function_call block. normalize_transcripts instead emits one FunctionCallEvidence for every block. An assistant entry with parallel tool calls therefore produces N calls but a single timeline FunctionCall event.

The current gates do not depend on this, because orphan detection reads only FunctionResult events. However the persisted timeline in results.json under-reports parallel calls, so any consumer that reconstructs execution order from the timeline sees incomplete data.

Consider emitting one timeline event per function_call block, with the sequence incremented per block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/tests/e2e/src/evidence.rs` around lines 461 - 478, Update
classify_entry to emit a timeline event for every assistant content block whose
type is "function_call" instead of selecting only the first via find. Adjust the
surrounding timeline construction to increment the sequence per emitted call,
preserving each block’s call_id and function_id so parallel calls are all
represented in results.json.

Comment on lines +444 to +485
let session_tree = context
.session_tree(session_id)
.await
.map_err(|error| collection_failure(FailurePhase::Collect, error.to_string()))?;
let session_transcripts = context
.transcripts_for_tree(&session_tree)
.await
.map_err(|error| collection_failure(FailurePhase::Collect, error.to_string()))?;
let statuses = context
.statuses_for_tree(&session_tree)
.await
.map_err(|error| collection_failure(FailurePhase::Collect, error.to_string()))?;
let transcript = session_transcripts
.iter()
.find(|item| item.session_id == session_id)
.map(|item| item.messages.clone())
.ok_or_else(|| {
collection_failure(
FailurePhase::Collect,
"root transcript is absent from session tree".into(),
)
})?;
let execution_evidence = build_evidence(
&root_terminal_status,
&session_tree,
&session_transcripts,
&statuses,
);
let structural_gates = evaluate_structural_gates(&metrics, &session_tree, &execution_evidence);
let response = common::final_response(&transcript);
let observation = ScenarioObservation {
metrics,
root_terminal_status,
session_tree,
session_transcripts,
execution_evidence,
transcript,
response,
};
report.transcript = Some(observation.transcript.clone());
report.metrics = Some(observation.metrics.clone());
let objective = (spec.evaluate)(context, &observation, run_id)
report.harness_evidence = Some(observation.execution_evidence.clone());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Persist each successful collection checkpoint.

Line 447 can return after metrics collection. Lines 451 and 455 can return after additional successful collection. In each case, E2eRunReport loses the already collected metrics and, after transcript collection, the root transcript.

Store report.metrics immediately after metrics collection. Extract and store the root transcript before requesting statuses. Keep harness_evidence conditional on complete collection.

Proposed fix
     let metrics = match context
         .wait_for_complete_metrics(spec.id, session_id, stuck_timeout, progress_interval)
         .await
@@
         }
     };
+    report.metrics = Some(metrics.clone());
+
     let session_tree = context
         .session_tree(session_id)
         .await
@@
         .await
         .map_err(|error| collection_failure(FailurePhase::Collect, error.to_string()))?;
-    let statuses = context
-        .statuses_for_tree(&session_tree)
-        .await
-        .map_err(|error| collection_failure(FailurePhase::Collect, error.to_string()))?;
     let transcript = session_transcripts
         .iter()
         .find(|item| item.session_id == session_id)
@@
             )
         })?;
+    report.transcript = Some(transcript.clone());
+
+    let statuses = context
+        .statuses_for_tree(&session_tree)
+        .await
+        .map_err(|error| collection_failure(FailurePhase::Collect, error.to_string()))?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/tests/e2e/src/suite.rs` around lines 444 - 485, Update the collection
flow around session_tree, session_transcripts, and statuses so each successful
checkpoint is persisted: assign report.metrics immediately after metrics
collection, extract the root transcript from session_transcripts and assign
report.transcript before requesting statuses, and reuse that transcript for
ScenarioObservation. Keep report.harness_evidence assigned only after
build_evidence completes successfully.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant