From 3bd56dbd847aed5e87b06fee74285cfdaefe5369 Mon Sep 17 00:00:00 2001 From: stefanskoricdev Date: Thu, 14 May 2026 18:03:17 +0200 Subject: [PATCH 1/2] agent_trace: Add git vcs metadata to built trace payloads Extend the Agent Trace model and builder to emit top-level vcs metadata from caller-provided commit metadata, with vcs.type fixed to git and vcs.revision mapped from commit_revision. Update hook wiring, golden fixtures, schema-validation tests, and context docs so the payload contract and optional-schema semantics stay aligned. Co-authored-by: SCE --- cli/src/services/agent_trace.rs | 15 +++++++ .../average_age_reconstruction/golden.json | 4 ++ .../file_rename_reconstruction/golden.json | 4 ++ .../hello_world_reconstruction/golden.json | 4 ++ .../mixed_change_reconstruction/golden.json | 4 ++ .../poem_edit_reconstruction/golden.json | 4 ++ .../poem_write_reconstruction/golden.json | 4 ++ .../golden.json | 4 ++ cli/src/services/agent_trace/tests.rs | 42 ++++++++++++++++++- cli/src/services/hooks/mod.rs | 1 + context/context-map.md | 2 +- context/glossary.md | 5 ++- .../agent-trace-embedded-schema-validation.md | 3 +- context/sce/agent-trace-minimal-generator.md | 15 +++++-- 14 files changed, 102 insertions(+), 9 deletions(-) diff --git a/cli/src/services/agent_trace.rs b/cli/src/services/agent_trace.rs index 4073c5d6..abe1b3b9 100644 --- a/cli/src/services/agent_trace.rs +++ b/cli/src/services/agent_trace.rs @@ -47,6 +47,15 @@ fn generate_agent_trace_id(commit_time: DateTime) -> Result #[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> { @@ -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, } @@ -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(), + }, files, }) } diff --git a/cli/src/services/agent_trace/fixtures/average_age_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/average_age_reconstruction/golden.json index 54a8dd37..ac2968a4 100644 --- a/cli/src/services/agent_trace/fixtures/average_age_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/average_age_reconstruction/golden.json @@ -3,6 +3,10 @@ "version": "0.1", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", + "vcs": { + "type": "git", + "revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889" + }, "files": [ { "path": "hunks/fib.ts", diff --git a/cli/src/services/agent_trace/fixtures/file_rename_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/file_rename_reconstruction/golden.json index c3cc9db3..5f3d8875 100644 --- a/cli/src/services/agent_trace/fixtures/file_rename_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/file_rename_reconstruction/golden.json @@ -3,5 +3,9 @@ "version": "0.1", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", + "vcs": { + "type": "git", + "revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889" + }, "files": [] } diff --git a/cli/src/services/agent_trace/fixtures/hello_world_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/hello_world_reconstruction/golden.json index 7d25492d..b7e4c90c 100644 --- a/cli/src/services/agent_trace/fixtures/hello_world_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/hello_world_reconstruction/golden.json @@ -3,6 +3,10 @@ "version": "0.1", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", + "vcs": { + "type": "git", + "revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889" + }, "files": [ { "path": "hunks/hello.ts", diff --git a/cli/src/services/agent_trace/fixtures/mixed_change_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/mixed_change_reconstruction/golden.json index 58745e42..424471f9 100644 --- a/cli/src/services/agent_trace/fixtures/mixed_change_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/mixed_change_reconstruction/golden.json @@ -3,6 +3,10 @@ "version": "0.1", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", + "vcs": { + "type": "git", + "revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889" + }, "files": [ { "path": ".version", diff --git a/cli/src/services/agent_trace/fixtures/poem_edit_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/poem_edit_reconstruction/golden.json index d6189c48..0b91bc1c 100644 --- a/cli/src/services/agent_trace/fixtures/poem_edit_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/poem_edit_reconstruction/golden.json @@ -3,6 +3,10 @@ "version": "0.1", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", + "vcs": { + "type": "git", + "revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889" + }, "files": [ { "path": "poem.md", diff --git a/cli/src/services/agent_trace/fixtures/poem_write_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/poem_write_reconstruction/golden.json index ead8d01c..e4aa8e44 100644 --- a/cli/src/services/agent_trace/fixtures/poem_write_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/poem_write_reconstruction/golden.json @@ -3,6 +3,10 @@ "version": "0.1", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", + "vcs": { + "type": "git", + "revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889" + }, "files": [ { "path": "poem.md", diff --git a/cli/src/services/agent_trace/fixtures/text_file_lifecycle_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/text_file_lifecycle_reconstruction/golden.json index 56deacad..7c5dd8bd 100644 --- a/cli/src/services/agent_trace/fixtures/text_file_lifecycle_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/text_file_lifecycle_reconstruction/golden.json @@ -3,6 +3,10 @@ "version": "0.1", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", + "vcs": { + "type": "git", + "revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889" + }, "files": [ { "path": "animals.txt", diff --git a/cli/src/services/agent_trace/tests.rs b/cli/src/services/agent_trace/tests.rs index 9812f5e2..4a930da3 100644 --- a/cli/src/services/agent_trace/tests.rs +++ b/cli/src/services/agent_trace/tests.rs @@ -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 { @@ -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 { fixtures @@ -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"]); } @@ -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"); @@ -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}" + ); +} diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index ce17ceff..ff094df2 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -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.")?; diff --git a/context/context-map.md b/context/context-map.md index 3d22add4..137948a4 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -46,7 +46,7 @@ Feature/domain context: - `context/sce/agent-trace-core-schema-migrations.md` (historical reference for removed local DB schema bootstrap behavior; T03 now implements the actual local DB with migrations) - `context/sce/agent-trace-retry-queue-observability.md` (inactive local-hook retry path plus historical retry/metrics reference) - `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` (T01 Local Hooks MVP production contract freeze and deterministic gap matrix for `agent-trace-local-hooks-production-mvp`) -- `context/sce/agent-trace-minimal-generator.md` (implemented library-only minimal agent-trace generator seam at `cli/src/services/agent_trace.rs`, producing a JSON payload with top-level `version`, UUIDv7 `id` derived from commit-time metadata, caller-provided commit-time `timestamp`, and per-file trace data from patch inputs via `intersect_patches(constructed_patch, post_commit_patch)` then `post_commit_patch`-anchored hunk classification into `ai`/`mixed`/`unknown` contributor categories, serialized per conversation as nested `contributor.type` plus one derived `ranges[{start_line,end_line}]` entry per post-commit hunk) +- `context/sce/agent-trace-minimal-generator.md` (implemented library-only minimal agent-trace generator seam at `cli/src/services/agent_trace.rs`, producing a JSON payload with top-level `version`, UUIDv7 `id` derived from commit-time metadata, caller-provided commit-time `timestamp`, top-level `vcs` metadata (`type = git`, `revision` from metadata input), and per-file trace data from patch inputs via `intersect_patches(constructed_patch, post_commit_patch)` then `post_commit_patch`-anchored hunk classification into `ai`/`mixed`/`unknown` contributor categories, serialized per conversation as nested `contributor.type` plus one derived `ranges[{start_line,end_line}]` entry per post-commit hunk) - `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: disabled-default commit-msg attribution, no-op `pre-commit`/`post-rewrite` entrypoints, active `post-commit` intersection entrypoint capturing current commit patch, querying recent `diff_traces` from past 7 days, combining valid patches via `patch::combine_patches`, intersecting via `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, and persisting built post-commit Agent Trace payloads to AgentTraceDb `agent_traces` (DB-only), plus `diff-trace` STDIN intake with required-field validation and dual persistence to AgentTraceDb and collision-safe `context/tmp/-000000-diff-trace.json` artifacts) - `context/sce/automated-profile-contract.md` (deterministic gate policy for automated OpenCode profile, including 10 gate categories, permission mappings, automated `/commit` single-commit execution behavior, and automated profile constraints) - `context/sce/bash-tool-policy-enforcement-contract.md` (approved bash-tool blocking contract plus the implementation target for generated OpenCode enforcement, including config schema, argv-prefix matching, fixed preset catalog/messages, and precedence rules) diff --git a/context/glossary.md b/context/glossary.md index 80e6e557..cfdcea7b 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -149,9 +149,10 @@ - `HunkContributor`: Enum in `cli/src/services/agent_trace.rs` classifying a `post_commit_patch` hunk's origin relative to the intersection patch `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`: `Ai` (exact line-by-line match), `Mixed` (same slot but different content), `Unknown` (no corresponding slot in `intersection_patch`); serialized as `snake_case` JSON strings - `Conversation`: Struct in `cli/src/services/agent_trace.rs` representing one per-hunk entry in the minimal agent-trace payload, carrying a nested `contributor` object (`{ "type": HunkContributor }`) plus `ranges`, where the current implementation emits exactly one `{ start_line, end_line }` entry derived from the `post_commit_patch` hunk - `TraceFile`: Struct in `cli/src/services/agent_trace.rs` representing one per-file entry in the minimal agent-trace payload, carrying `path` (from `post_commit_patch`'s `new_path`) plus `conversations` (one per `post_commit_patch` hunk) -- `AgentTrace`: Top-level struct in `cli/src/services/agent_trace.rs` representing the minimal agent-trace payload, carrying top-level `version` (fixed to `v0.1.0`), `id` (UUIDv7 string derived from the same commit-time moment used for `timestamp` in `build_agent_trace(...)`), `timestamp` (caller-provided commit timestamp via `AgentTraceMetadataInput.commit_timestamp`, validated as RFC 3339), and `files` (`Vec`, one per `post_commit_patch` file); `serde`-serializable with `snake_case` field naming +- `AgentTraceVcs`: Top-level VCS metadata struct in `cli/src/services/agent_trace.rs` carrying `type` and `revision`; current builder behavior fixes `type` to `"git"` and maps revision from caller metadata. +- `AgentTrace`: Top-level struct in `cli/src/services/agent_trace.rs` representing the minimal agent-trace payload, carrying top-level `version` (fixed to `0.1`), `id` (UUIDv7 string derived from the same commit-time moment used for `timestamp` in `build_agent_trace(...)`), `timestamp` (caller-provided commit timestamp via `AgentTraceMetadataInput.commit_timestamp`, validated as RFC 3339), `vcs` (`AgentTraceVcs`), and `files` (`Vec`, one per `post_commit_patch` file); `serde`-serializable with `snake_case` field naming - `classify_hunk`: Public function in `cli/src/services/agent_trace.rs` that classifies a single `post_commit_patch` hunk against `intersection_patch` hunks by matching on `old_start` slot, returning `HunkContributor::Ai` for exact line-by-line match, `Mixed` for same-slot-but-different-content, or `Unknown` when no matching slot exists -- `AgentTraceMetadataInput`: Metadata input struct in `cli/src/services/agent_trace.rs` that currently carries `commit_timestamp`, an RFC 3339 commit-time value required by `build_agent_trace` and used as `AgentTrace.timestamp` +- `AgentTraceMetadataInput`: Metadata input struct in `cli/src/services/agent_trace.rs` that carries `commit_timestamp` (RFC 3339 commit-time value used as `AgentTrace.timestamp`) and `commit_revision` (mapped to `AgentTrace.vcs.revision`). - `build_agent_trace`: Public function in `cli/src/services/agent_trace.rs` that computes `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`, iterates over `post_commit_patch`'s files and hunks, classifies each hunk against `intersection_patch`, validates `AgentTraceMetadataInput.commit_timestamp` as RFC 3339, derives UUIDv7 `AgentTrace.id` from that same commit-time moment, and returns `Result` with top-level metadata fields plus one `Conversation` per `post_commit_patch` hunk; library-only, not wired into CLI command dispatch - `agent-trace plugin diff extraction seam`: Internal helper `extractDiffTracePayload` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that reads `input.event` and returns `{ sessionID, diff, time }` only when the event is `session.diff`; extracts `sessionID` from `properties.sessionID` (falling back to `"unknown"` when missing/empty), joins non-empty `patch` or `diff` fields from `properties.diff[]` entries into a single `diff` string, and uses `Date.now()` for `time`; returns `undefined` for non-`session.diff` events or empty diff arrays. - `agent-trace plugin diff-trace hook handoff seam`: Internal helper `runDiffTraceHook` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that invokes `sce hooks diff-trace`, streams extracted `{ sessionID, diff, time }` to STDIN JSON, and surfaces deterministic invocation failures. diff --git a/context/sce/agent-trace-embedded-schema-validation.md b/context/sce/agent-trace-embedded-schema-validation.md index 14c8f63a..94b10800 100644 --- a/context/sce/agent-trace-embedded-schema-validation.md +++ b/context/sce/agent-trace-embedded-schema-validation.md @@ -14,6 +14,7 @@ Current internal validation seam for Agent Trace JSON in the Rust CLI. - `agent_trace_schema_validator()` compiles the embedded schema once and caches the `jsonschema::Validator` in a `OnceLock`. - `validate_agent_trace_value(&serde_json::Value)` validates already-parsed JSON values against the embedded schema. - `validate_agent_trace_json(&str)` parses a JSON string and then validates it against the embedded schema. +- Top-level `vcs` is optional at schema level; payloads without `vcs` validate, and payloads that include `vcs` must still provide both `type` and `revision`. - Validation failures use `AgentTraceValidationError` to distinguish: - invalid JSON input - schema-validation failures @@ -27,6 +28,6 @@ Current internal validation seam for Agent Trace JSON in the Rust CLI. ## Verification -- Focused tests cover embedded schema parse/compile, valid schema-shaped JSON-string acceptance, direct `serde_json::Value` validation, and representative schema-invalid rejection. +- Focused tests cover embedded schema parse/compile, valid schema-shaped JSON-string acceptance, direct `serde_json::Value` validation (including records without top-level `vcs`), and representative schema-invalid rejection (including `vcs` objects missing `revision`). See also: [agent-trace-minimal-generator.md](./agent-trace-minimal-generator.md), [../context-map.md](../context-map.md) diff --git a/context/sce/agent-trace-minimal-generator.md b/context/sce/agent-trace-minimal-generator.md index df00e315..9dc5b03b 100644 --- a/context/sce/agent-trace-minimal-generator.md +++ b/context/sce/agent-trace-minimal-generator.md @@ -23,7 +23,8 @@ Given a `constructed_patch` (AI candidate) and a `post_commit_patch` (canonical | `LineRange` | New-file line span with `start_line` + `end_line` | | `Conversation` | Per-hunk entry: nested contributor + `ranges` (currently exactly one range derived from `post_commit_patch`) | | `TraceFile` | Per-file entry: path + conversations | -| `AgentTrace` | Top-level payload: `version`, `id`, `timestamp`, `files` | +| `AgentTraceVcs` | Top-level VCS metadata object carrying `type` + `revision` | +| `AgentTrace` | Top-level payload: `version`, `id`, `timestamp`, `vcs`, `files` | All types are `serde`-serializable with `snake_case` field naming. `Conversation.contributor` serializes as a nested object with a JSON field named `type`. @@ -31,15 +32,21 @@ All types are `serde`-serializable with `snake_case` field naming. `Conversation Current output includes top-level metadata fields with this contract: -- `version` is fixed to `"v0.1.0"` +- `version` is fixed to `"0.1"` - `id` is generated per `build_agent_trace(...)` call as a UUIDv7 string derived from the same commit-time moment used for `timestamp` - `timestamp` is sourced from explicit commit metadata input (`AgentTraceMetadataInput.commit_timestamp`) and must be RFC 3339 +- `vcs.type` is fixed to `"git"` +- `vcs.revision` is sourced from explicit commit metadata input (`AgentTraceMetadataInput.commit_revision`) ```json { - "version": "v0.1.0", + "version": "0.1", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", + "vcs": { + "type": "git", + "revision": "a0b1c2d3e4f5a6b7c8d9e0f11223344556677889" + }, "files": [ { "path": "src/example.ts", @@ -62,7 +69,7 @@ Current output includes top-level metadata fields with this contract: ## Public API - `classify_hunk(post_commit_hunk, intersection_hunks) -> HunkContributor` — classify a single `post_commit_patch` hunk against `intersection_patch` hunks. -- `build_agent_trace(constructed_patch, post_commit_patch, metadata) -> Result` — full generator entrypoint that validates `metadata.commit_timestamp` as RFC 3339, uses it as top-level `timestamp`, and derives a UUIDv7 `id` from that same commit-time moment. +- `build_agent_trace(constructed_patch, post_commit_patch, metadata) -> Result` — full generator entrypoint that validates `metadata.commit_timestamp` as RFC 3339, uses it as top-level `timestamp`, derives a UUIDv7 `id` from that same commit-time moment, sets `vcs.type = "git"`, and maps `metadata.commit_revision` to `vcs.revision`. ## Test fixture contract From 1b85f6ef0e3bf79aa45199c9e2509535ae41d15a Mon Sep 17 00:00:00 2001 From: stefanskoricdev Date: Thu, 14 May 2026 18:27:10 +0200 Subject: [PATCH 2/2] agent-trace: Enforce strict x.y.z version format Update the Agent Trace runtime default and schema pattern to require three-part numeric semver-style versions. Align golden fixtures and context docs so generated payloads, validation behavior, and documented contracts stay consistent. Co-authored-by: SCE --- cli/src/services/agent_trace.rs | 2 +- .../fixtures/average_age_reconstruction/golden.json | 2 +- .../fixtures/file_rename_reconstruction/golden.json | 2 +- .../fixtures/hello_world_reconstruction/golden.json | 2 +- .../fixtures/mixed_change_reconstruction/golden.json | 2 +- .../agent_trace/fixtures/poem_edit_reconstruction/golden.json | 2 +- .../fixtures/poem_write_reconstruction/golden.json | 2 +- .../fixtures/text_file_lifecycle_reconstruction/golden.json | 2 +- config/schema/agent-trace.schema.json | 4 ++-- context/glossary.md | 2 +- context/sce/agent-trace-embedded-schema-validation.md | 3 ++- context/sce/agent-trace-minimal-generator.md | 4 ++-- 12 files changed, 15 insertions(+), 14 deletions(-) diff --git a/cli/src/services/agent_trace.rs b/cli/src/services/agent_trace.rs index abe1b3b9..eca0a348 100644 --- a/cli/src/services/agent_trace.rs +++ b/cli/src/services/agent_trace.rs @@ -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() diff --git a/cli/src/services/agent_trace/fixtures/average_age_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/average_age_reconstruction/golden.json index ac2968a4..e139677c 100644 --- a/cli/src/services/agent_trace/fixtures/average_age_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/average_age_reconstruction/golden.json @@ -1,6 +1,6 @@ { "$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": { diff --git a/cli/src/services/agent_trace/fixtures/file_rename_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/file_rename_reconstruction/golden.json index 5f3d8875..476f93f0 100644 --- a/cli/src/services/agent_trace/fixtures/file_rename_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/file_rename_reconstruction/golden.json @@ -1,6 +1,6 @@ { "$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": { diff --git a/cli/src/services/agent_trace/fixtures/hello_world_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/hello_world_reconstruction/golden.json index b7e4c90c..0806e931 100644 --- a/cli/src/services/agent_trace/fixtures/hello_world_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/hello_world_reconstruction/golden.json @@ -1,6 +1,6 @@ { "$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": { diff --git a/cli/src/services/agent_trace/fixtures/mixed_change_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/mixed_change_reconstruction/golden.json index 424471f9..51aabb70 100644 --- a/cli/src/services/agent_trace/fixtures/mixed_change_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/mixed_change_reconstruction/golden.json @@ -1,6 +1,6 @@ { "$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": { diff --git a/cli/src/services/agent_trace/fixtures/poem_edit_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/poem_edit_reconstruction/golden.json index 0b91bc1c..c75adc95 100644 --- a/cli/src/services/agent_trace/fixtures/poem_edit_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/poem_edit_reconstruction/golden.json @@ -1,6 +1,6 @@ { "$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": { diff --git a/cli/src/services/agent_trace/fixtures/poem_write_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/poem_write_reconstruction/golden.json index e4aa8e44..c536c0f3 100644 --- a/cli/src/services/agent_trace/fixtures/poem_write_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/poem_write_reconstruction/golden.json @@ -1,6 +1,6 @@ { "$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": { diff --git a/cli/src/services/agent_trace/fixtures/text_file_lifecycle_reconstruction/golden.json b/cli/src/services/agent_trace/fixtures/text_file_lifecycle_reconstruction/golden.json index 7c5dd8bd..1db1ac91 100644 --- a/cli/src/services/agent_trace/fixtures/text_file_lifecycle_reconstruction/golden.json +++ b/cli/src/services/agent_trace/fixtures/text_file_lifecycle_reconstruction/golden.json @@ -1,6 +1,6 @@ { "$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": { diff --git a/config/schema/agent-trace.schema.json b/config/schema/agent-trace.schema.json index c886d8fb..5ff94911 100644 --- a/config/schema/agent-trace.schema.json +++ b/config/schema/agent-trace.schema.json @@ -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", diff --git a/context/glossary.md b/context/glossary.md index cfdcea7b..0097de94 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -150,7 +150,7 @@ - `Conversation`: Struct in `cli/src/services/agent_trace.rs` representing one per-hunk entry in the minimal agent-trace payload, carrying a nested `contributor` object (`{ "type": HunkContributor }`) plus `ranges`, where the current implementation emits exactly one `{ start_line, end_line }` entry derived from the `post_commit_patch` hunk - `TraceFile`: Struct in `cli/src/services/agent_trace.rs` representing one per-file entry in the minimal agent-trace payload, carrying `path` (from `post_commit_patch`'s `new_path`) plus `conversations` (one per `post_commit_patch` hunk) - `AgentTraceVcs`: Top-level VCS metadata struct in `cli/src/services/agent_trace.rs` carrying `type` and `revision`; current builder behavior fixes `type` to `"git"` and maps revision from caller metadata. -- `AgentTrace`: Top-level struct in `cli/src/services/agent_trace.rs` representing the minimal agent-trace payload, carrying top-level `version` (fixed to `0.1`), `id` (UUIDv7 string derived from the same commit-time moment used for `timestamp` in `build_agent_trace(...)`), `timestamp` (caller-provided commit timestamp via `AgentTraceMetadataInput.commit_timestamp`, validated as RFC 3339), `vcs` (`AgentTraceVcs`), and `files` (`Vec`, one per `post_commit_patch` file); `serde`-serializable with `snake_case` field naming +- `AgentTrace`: Top-level struct in `cli/src/services/agent_trace.rs` representing the minimal agent-trace payload, carrying top-level `version` (fixed to `0.1.0`, strict numeric `x.y.z`), `id` (UUIDv7 string derived from the same commit-time moment used for `timestamp` in `build_agent_trace(...)`), `timestamp` (caller-provided commit timestamp via `AgentTraceMetadataInput.commit_timestamp`, validated as RFC 3339), `vcs` (`AgentTraceVcs`), and `files` (`Vec`, one per `post_commit_patch` file); `serde`-serializable with `snake_case` field naming - `classify_hunk`: Public function in `cli/src/services/agent_trace.rs` that classifies a single `post_commit_patch` hunk against `intersection_patch` hunks by matching on `old_start` slot, returning `HunkContributor::Ai` for exact line-by-line match, `Mixed` for same-slot-but-different-content, or `Unknown` when no matching slot exists - `AgentTraceMetadataInput`: Metadata input struct in `cli/src/services/agent_trace.rs` that carries `commit_timestamp` (RFC 3339 commit-time value used as `AgentTrace.timestamp`) and `commit_revision` (mapped to `AgentTrace.vcs.revision`). - `build_agent_trace`: Public function in `cli/src/services/agent_trace.rs` that computes `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`, iterates over `post_commit_patch`'s files and hunks, classifies each hunk against `intersection_patch`, validates `AgentTraceMetadataInput.commit_timestamp` as RFC 3339, derives UUIDv7 `AgentTrace.id` from that same commit-time moment, and returns `Result` with top-level metadata fields plus one `Conversation` per `post_commit_patch` hunk; library-only, not wired into CLI command dispatch diff --git a/context/sce/agent-trace-embedded-schema-validation.md b/context/sce/agent-trace-embedded-schema-validation.md index 94b10800..845e20ce 100644 --- a/context/sce/agent-trace-embedded-schema-validation.md +++ b/context/sce/agent-trace-embedded-schema-validation.md @@ -14,6 +14,7 @@ Current internal validation seam for Agent Trace JSON in the Rust CLI. - `agent_trace_schema_validator()` compiles the embedded schema once and caches the `jsonschema::Validator` in a `OnceLock`. - `validate_agent_trace_value(&serde_json::Value)` validates already-parsed JSON values against the embedded schema. - `validate_agent_trace_json(&str)` parses a JSON string and then validates it against the embedded schema. +- Top-level `version` must match strict numeric `x.y.z` (`^[0-9]+\.[0-9]+\.[0-9]+$`); two-part values like `x.y` are rejected. - Top-level `vcs` is optional at schema level; payloads without `vcs` validate, and payloads that include `vcs` must still provide both `type` and `revision`. - Validation failures use `AgentTraceValidationError` to distinguish: - invalid JSON input @@ -28,6 +29,6 @@ Current internal validation seam for Agent Trace JSON in the Rust CLI. ## Verification -- Focused tests cover embedded schema parse/compile, valid schema-shaped JSON-string acceptance, direct `serde_json::Value` validation (including records without top-level `vcs`), and representative schema-invalid rejection (including `vcs` objects missing `revision`). +- Focused tests cover embedded schema parse/compile, valid schema-shaped JSON-string acceptance, direct `serde_json::Value` validation (including records without top-level `vcs`), and representative schema-invalid rejection (including two-part `version` values and `vcs` objects missing `revision`). See also: [agent-trace-minimal-generator.md](./agent-trace-minimal-generator.md), [../context-map.md](../context-map.md) diff --git a/context/sce/agent-trace-minimal-generator.md b/context/sce/agent-trace-minimal-generator.md index 9dc5b03b..1eff934d 100644 --- a/context/sce/agent-trace-minimal-generator.md +++ b/context/sce/agent-trace-minimal-generator.md @@ -32,7 +32,7 @@ All types are `serde`-serializable with `snake_case` field naming. `Conversation Current output includes top-level metadata fields with this contract: -- `version` is fixed to `"0.1"` +- `version` is fixed to `"0.1.0"` and follows strict numeric `x.y.z` - `id` is generated per `build_agent_trace(...)` call as a UUIDv7 string derived from the same commit-time moment used for `timestamp` - `timestamp` is sourced from explicit commit metadata input (`AgentTraceMetadataInput.commit_timestamp`) and must be RFC 3339 - `vcs.type` is fixed to `"git"` @@ -40,7 +40,7 @@ Current output includes top-level metadata fields with this contract: ```json { - "version": "0.1", + "version": "0.1.0", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", "vcs": {