Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion cli/src/services/agent_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use super::patch::{
TouchedLineKind,
};

pub const AGENT_TRACE_VERSION: &str = "0.1";
pub const AGENT_TRACE_VERSION: &str = "0.1.0";

fn default_agent_trace_version() -> String {
AGENT_TRACE_VERSION.to_owned()
Expand All @@ -47,6 +47,15 @@ fn generate_agent_trace_id(commit_time: DateTime<FixedOffset>) -> Result<String>
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AgentTraceMetadataInput<'a> {
pub commit_timestamp: &'a str,
pub commit_revision: &'a str,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct AgentTraceVcs {
#[serde(rename = "type")]
pub kind: String,
pub revision: String,
}

fn parse_commit_timestamp(commit_timestamp: &str) -> Result<DateTime<FixedOffset>> {
Expand Down Expand Up @@ -204,6 +213,8 @@ pub struct AgentTrace {
/// RFC 3339 timestamp string sourced from caller-provided commit metadata.
#[serde(default)]
pub timestamp: String,
/// Version control metadata sourced from caller-provided commit metadata.
pub vcs: AgentTraceVcs,
/// File-level trace entries, one per file present in `post_commit_patch`.
pub files: Vec<TraceFile>,
}
Expand Down Expand Up @@ -371,6 +382,10 @@ pub fn build_agent_trace(
version: default_agent_trace_version(),
id: generate_agent_trace_id(commit_time)?,
timestamp,
vcs: AgentTraceVcs {
kind: "git".to_owned(),
revision: metadata.commit_revision.to_owned(),
},
Comment on lines +385 to +388
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate commit revision before emitting trace payload.

Line 387 copies caller-provided revision without validation. An empty/whitespace revision can silently persist unusable VCS metadata.

🛡️ Proposed fix
-use anyhow::{Context, Result};
+use anyhow::{ensure, Context, Result};
 pub fn build_agent_trace(
     constructed_patch: &ParsedPatch,
     post_commit_patch: &ParsedPatch,
     metadata: AgentTraceMetadataInput<'_>,
 ) -> Result<AgentTrace> {
+    ensure!(
+        !metadata.commit_revision.trim().is_empty(),
+        "Invalid commit revision: expected non-empty commit OID."
+    );
     let commit_time = parse_commit_timestamp(metadata.commit_timestamp)?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vcs: AgentTraceVcs {
kind: "git".to_owned(),
revision: metadata.commit_revision.to_owned(),
},
use anyhow::{ensure, Context, Result};
pub fn build_agent_trace(
constructed_patch: &ParsedPatch,
post_commit_patch: &ParsedPatch,
metadata: AgentTraceMetadataInput<'_>,
) -> Result<AgentTrace> {
ensure!(
!metadata.commit_revision.trim().is_empty(),
"Invalid commit revision: expected non-empty commit OID."
);
let commit_time = parse_commit_timestamp(metadata.commit_timestamp)?;
// ... rest of function ...
vcs: AgentTraceVcs {
kind: "git".to_owned(),
revision: metadata.commit_revision.to_owned(),
},
🤖 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 `@cli/src/services/agent_trace.rs` around lines 385 - 388, The code assigns
metadata.commit_revision directly into AgentTraceVcs.revision which can persist
empty/whitespace revision strings; trim and validate metadata.commit_revision
before emitting the trace, and only populate the revision when non-empty. Update
the AgentTraceVcs type to use Option<String> for revision (if it isn't already)
and, in the construction code that sets vcs: AgentTraceVcs { kind:
"git".to_owned(), revision: ... }, do something like let rev =
metadata.commit_revision.trim(); revision: if rev.is_empty() { None } else {
Some(rev.to_owned()) } so the payload omits invalid revisions. Ensure downstream
serialization/consumers still handle the Option case.

files,
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"$schema": "https://agent-trace.dev/schemas/v1/trace-record.json",
"version": "0.1",
"version": "0.1.0",
"id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1",
"timestamp": "2026-04-23T10:20:30Z",
"vcs": {
"type": "git",
"revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889"
},
"files": [
{
"path": "hunks/fib.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"$schema": "https://agent-trace.dev/schemas/v1/trace-record.json",
"version": "0.1",
"version": "0.1.0",
"id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1",
"timestamp": "2026-04-23T10:20:30Z",
"vcs": {
"type": "git",
"revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889"
},
"files": []
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"$schema": "https://agent-trace.dev/schemas/v1/trace-record.json",
"version": "0.1",
"version": "0.1.0",
"id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1",
"timestamp": "2026-04-23T10:20:30Z",
"vcs": {
"type": "git",
"revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889"
},
"files": [
{
"path": "hunks/hello.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"$schema": "https://agent-trace.dev/schemas/v1/trace-record.json",
"version": "0.1",
"version": "0.1.0",
"id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1",
"timestamp": "2026-04-23T10:20:30Z",
"vcs": {
"type": "git",
"revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889"
},
"files": [
{
"path": ".version",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"$schema": "https://agent-trace.dev/schemas/v1/trace-record.json",
"version": "0.1",
"version": "0.1.0",
"id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1",
"timestamp": "2026-04-23T10:20:30Z",
"vcs": {
"type": "git",
"revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889"
},
"files": [
{
"path": "poem.md",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"$schema": "https://agent-trace.dev/schemas/v1/trace-record.json",
"version": "0.1",
"version": "0.1.0",
"id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1",
"timestamp": "2026-04-23T10:20:30Z",
"vcs": {
"type": "git",
"revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889"
},
"files": [
{
"path": "poem.md",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"$schema": "https://agent-trace.dev/schemas/v1/trace-record.json",
"version": "0.1",
"version": "0.1.0",
"id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1",
"timestamp": "2026-04-23T10:20:30Z",
"vcs": {
"type": "git",
"revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889"
},
"files": [
{
"path": "animals.txt",
Expand Down
42 changes: 41 additions & 1 deletion cli/src/services/agent_trace/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::{
AGENT_TRACE_VERSION,
};
use crate::services::patch::{combine_patches, parse_patch, ParsedPatch};
use serde_json::Value;
use serde_json::{json, Value};

#[derive(Clone, Copy)]
struct AgentTraceScenario {
Expand All @@ -13,6 +13,7 @@ struct AgentTraceScenario {
}

const TEST_COMMIT_TIMESTAMP: &str = "2026-04-23T10:20:30Z";
const TEST_COMMIT_REVISION: &str = "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889";

fn parse_fixtures(fixtures: &[&str]) -> Vec<ParsedPatch> {
fixtures
Expand Down Expand Up @@ -60,13 +61,17 @@ fn assert_builds_expected_agent_trace(scenario: AgentTraceScenario) {
&post_commit_patch,
AgentTraceMetadataInput {
commit_timestamp: TEST_COMMIT_TIMESTAMP,
commit_revision: TEST_COMMIT_REVISION,
},
)
.expect("agent trace should build");
assert_eq!(actual.version, AGENT_TRACE_VERSION);
assert_eq!(actual.timestamp, TEST_COMMIT_TIMESTAMP);
assert_eq!(actual.vcs.kind, "git");
assert_eq!(actual.vcs.revision, TEST_COMMIT_REVISION);
let actual_json = serde_json::to_value(&actual).expect("agent trace should serialize");
validate_agent_trace_value(&actual_json).expect("actual json should validate against schema");
assert_eq!(actual_json["vcs"], golden["vcs"]);
assert_eq!(actual_json["files"], golden["files"]);
}

Expand Down Expand Up @@ -140,6 +145,7 @@ fn poem_edit_reconstruction_maps_each_hunk_to_one_range() {
&post_commit_patch,
AgentTraceMetadataInput {
commit_timestamp: TEST_COMMIT_TIMESTAMP,
commit_revision: TEST_COMMIT_REVISION,
},
)
.expect("agent trace should build");
Expand Down Expand Up @@ -203,3 +209,37 @@ fn file_rename_reconstruction_matches_golden_agent_trace() {
golden: include_str!("fixtures/file_rename_reconstruction/golden.json"),
});
}

#[test]
fn schema_validation_allows_agent_trace_without_vcs() {
let value = json!({
"version": AGENT_TRACE_VERSION,
"id": "0196f25d-cf7f-7ca8-a652-8562c8a9f1d5",
"timestamp": TEST_COMMIT_TIMESTAMP,
"files": []
});

validate_agent_trace_value(&value)
.expect("agent trace without vcs should validate against schema");
}

#[test]
fn schema_validation_rejects_vcs_missing_revision() {
let value = json!({
"version": AGENT_TRACE_VERSION,
"id": "0196f25d-cf7f-7ca8-a652-8562c8a9f1d5",
"timestamp": TEST_COMMIT_TIMESTAMP,
"vcs": {
"type": "git"
},
"files": []
});

let error = validate_agent_trace_value(&value)
.expect_err("agent trace with vcs missing revision should fail validation");
let rendered = error.to_string();
assert!(
rendered.contains("\"revision\" is a required property"),
"expected vcs/revision validation failure, got: {rendered}"
);
}
1 change: 1 addition & 0 deletions cli/src/services/hooks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ where
&flow_result.post_commit_data.parsed_patch,
AgentTraceMetadataInput {
commit_timestamp: &commit_timestamp,
commit_revision: &flow_result.post_commit_data.commit_oid,
},
)
.context("Failed to build Agent Trace payload from post-commit intersection flow result.")?;
Expand Down
4 changes: 2 additions & 2 deletions config/schema/agent-trace.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"properties": {
"version": {
"type": "string",
"pattern": "^[0-9]+\\.[0-9]+$",
"description": "Agent Trace specification version (e.g., '1.0')"
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
"description": "Agent Trace specification version (e.g., '1.0.0')"
},
"id": {
"type": "string",
Expand Down
2 changes: 1 addition & 1 deletion context/context-map.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions context/glossary.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion context/sce/agent-trace-embedded-schema-validation.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 11 additions & 4 deletions context/sce/agent-trace-minimal-generator.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.