From 2e642cb8f92515657c996d55835b911bfe8b0cc1 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 26 Oct 2026 08:58:06 +0000 Subject: [PATCH] simplify(search-eval): embed the canonical corpus Restack unique #4530 onto the rewritten #4528 tip. --- .gitattributes | 9 +- crates/tracedecay-query/BUILD.bazel | 29 +- .../evals/agent_adoption/fixture/Cargo.lock | 7 - .../context_eval_project/src/auth/login.rs | 19 - .../context_eval_project/src/auth/session.rs | 29 - .../context_eval_project/src/net/retry.rs | 45 - .../src/storage/config_store.rs | 42 - .../tests/fixtures/sample.dockerfile | 37 - .../corpus/cargo-slot/src/main.rust.fixture | 6 - .../tracedecay-domain/src/integration.rs | 652 ----- .../tracedecay-domain/src/repository.rs | 639 ----- .../src/research/canonical.rs | 158 -- .../src/research/coverage.rs | 672 ------ .../tracedecay-domain/src/research/error.rs | 38 - .../tracedecay-domain/src/research/time.rs | 25 - .../src/research/watermark.rs | 50 - .../crates/tracedecay-domain/src/session.rs | 2114 ----------------- .../src/search_quality/packaged.rs | 38 +- .../src/packaged_assets.rs | 94 + .../src/report_tests.rs | 2 +- evals/agent_adoption/BUILD.bazel | 8 + scripts/bazel/gen_builds.py | 26 +- scripts/test-gen-builds-default-alias.py | 35 + tests/BUILD.bazel | 72 +- .../query-lexical-graph-workload-v1.json | 0 25 files changed, 262 insertions(+), 4584 deletions(-) delete mode 100644 crates/tracedecay-query/assets/runtime-root/evals/agent_adoption/fixture/Cargo.lock delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/auth/login.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/auth/session.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/net/retry.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/storage/config_store.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/sample.dockerfile delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/cargo-slot/src/main.rust.fixture delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/integration.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/repository.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/canonical.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/coverage.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/error.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/time.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/watermark.rs delete mode 100644 crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/session.rs rename {crates/tracedecay-query/assets/runtime-root/tests => tests}/fixtures/search_quality/query-lexical-graph-workload-v1.json (100%) diff --git a/.gitattributes b/.gitattributes index be8151c047..6b158206fe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,13 +10,10 @@ tests/fixtures/** text eol=lf # schema-shape digests; a CRLF checkout rewrites those definitions and makes # a released shape read as foreign. *.sql text eol=lf -# The search-quality candidate workload pins each corpus document against the -# blob in the pinned source tree, and the packaged evaluator assets are pulled -# into the binary with include_bytes! under a pinned digest. One corpus -# document lives outside tests/fixtures, and the packaged mirror is a separate -# tree; a CRLF checkout rewrites either and fails both byte-exact contracts. +# The search-quality workload pins each corpus document against the blob in +# its source tree; the packaged evaluator embeds those same fixture bytes. +# One corpus document lives outside tests/fixtures, so enforce LF there too. evals/agent_adoption/fixture/** text eol=lf -crates/tracedecay-query/assets/runtime-root/** text eol=lf # Force LF for bundled plugin skill docs; the skill hygiene tests assert the # canonical source files are LF-only, and Windows checkout otherwise rewrites # them before the lint runs. diff --git a/crates/tracedecay-query/BUILD.bazel b/crates/tracedecay-query/BUILD.bazel index e1499a70d6..e727bf8982 100644 --- a/crates/tracedecay-query/BUILD.bazel +++ b/crates/tracedecay-query/BUILD.bazel @@ -15,7 +15,7 @@ rust_library( crate_name = "tracedecay_query", edition = crate_edition(), compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) - + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True), + + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-code-index:tracedecay-code-index__bare__d5f4099fe25", "//crates/tracedecay-contracts:tracedecay-contracts", "//crates/tracedecay-domain:tracedecay-domain", "//crates/tracedecay-graph-db:tracedecay-graph-db", "//crates/tracedecay-private-fs:tracedecay-private-fs", "//crates/tracedecay-runtime-core:tracedecay-runtime-core", "//crates/tracedecay-temporal-query:tracedecay-temporal-query"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -31,7 +31,7 @@ rust_library( crate_name = "tracedecay_query", edition = crate_edition(), compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) - + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True), + + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-code-index:tracedecay-code-index__+eval-helpers_+lang-dockerfile_+test-helpers__dev", "//crates/tracedecay-contracts:tracedecay-contracts__dev", "//crates/tracedecay-domain:tracedecay-domain__dev", "//crates/tracedecay-graph-db:tracedecay-graph-db__+eval-helpers_+test-helpers__dev", "//crates/tracedecay-private-fs:tracedecay-private-fs__+test-helpers__dev", "//crates/tracedecay-runtime-core:tracedecay-runtime-core__+test-helpers_+test-transport__dev", "//crates/tracedecay-temporal-query:tracedecay-temporal-query__+test-helpers__dev"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -47,7 +47,7 @@ rust_library( crate_name = "tracedecay_query", edition = crate_edition(), compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) - + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True), + + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-code-index:tracedecay-code-index__-default_-medium__d6a7dfc4236", "//crates/tracedecay-contracts:tracedecay-contracts", "//crates/tracedecay-domain:tracedecay-domain", "//crates/tracedecay-graph-db:tracedecay-graph-db", "//crates/tracedecay-private-fs:tracedecay-private-fs", "//crates/tracedecay-runtime-core:tracedecay-runtime-core", "//crates/tracedecay-temporal-query:tracedecay-temporal-query"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -63,7 +63,7 @@ rust_library( crate_name = "tracedecay_query", edition = crate_edition(), compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) - + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True), + + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-code-index:tracedecay-code-index__+eval-helpers_-default_-full_-lang-markdown_-lite_-medium__d6f240ed6cb", "//crates/tracedecay-contracts:tracedecay-contracts", "//crates/tracedecay-domain:tracedecay-domain", "//crates/tracedecay-graph-db:tracedecay-graph-db__+eval-helpers__d73c9d40925", "//crates/tracedecay-private-fs:tracedecay-private-fs", "//crates/tracedecay-runtime-core:tracedecay-runtime-core__db496041207", "//crates/tracedecay-temporal-query:tracedecay-temporal-query"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -79,7 +79,7 @@ rust_library( crate_name = "tracedecay_query", edition = crate_edition(), compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) - + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True), + + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-code-index:tracedecay-code-index__bare__d455cac0e1d", "//crates/tracedecay-contracts:tracedecay-contracts", "//crates/tracedecay-domain:tracedecay-domain", "//crates/tracedecay-graph-db:tracedecay-graph-db", "//crates/tracedecay-private-fs:tracedecay-private-fs", "//crates/tracedecay-runtime-core:tracedecay-runtime-core", "//crates/tracedecay-temporal-query:tracedecay-temporal-query"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -95,7 +95,7 @@ rust_library( crate_name = "tracedecay_query", edition = crate_edition(), compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) - + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True), + + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-code-index:tracedecay-code-index__-default_-medium__d6a7dfc4236", "//crates/tracedecay-contracts:tracedecay-contracts", "//crates/tracedecay-domain:tracedecay-domain", "//crates/tracedecay-graph-db:tracedecay-graph-db", "//crates/tracedecay-private-fs:tracedecay-private-fs", "//crates/tracedecay-runtime-core:tracedecay-runtime-core", "//crates/tracedecay-temporal-query:tracedecay-temporal-query"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -111,7 +111,7 @@ rust_library( crate_name = "tracedecay_query", edition = crate_edition(), compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) - + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True), + + glob(["assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-code-index:tracedecay-code-index__-default_-full_-medium__d480e0ce7ad", "//crates/tracedecay-contracts:tracedecay-contracts", "//crates/tracedecay-domain:tracedecay-domain", "//crates/tracedecay-graph-db:tracedecay-graph-db", "//crates/tracedecay-private-fs:tracedecay-private-fs", "//crates/tracedecay-runtime-core:tracedecay-runtime-core", "//crates/tracedecay-temporal-query:tracedecay-temporal-query"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -125,7 +125,8 @@ rust_binary( srcs = glob(["src/**/*.rs"]), crate_root = "src/bin/tracedecay_index_bench.rs", edition = crate_edition(), - compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True), + compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) + + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-query:tracedecay-query__+production__d56d68a5bbd", "//crates/tracedecay-code-index:tracedecay-code-index__-default_-medium__d6a7dfc4236", "//crates/tracedecay-contracts:tracedecay-contracts", "//crates/tracedecay-domain:tracedecay-domain", "//crates/tracedecay-graph-db:tracedecay-graph-db", "//crates/tracedecay-private-fs:tracedecay-private-fs", "//crates/tracedecay-runtime-core:tracedecay-runtime-core", "//crates/tracedecay-temporal-query:tracedecay-temporal-query"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -142,7 +143,8 @@ rust_binary( srcs = glob(["src/**/*.rs"]), crate_root = "src/bin/tracedecay_index_bench.rs", edition = crate_edition(), - compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True), + compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) + + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-query:tracedecay-query__+production_+test-helpers__dev", "//crates/tracedecay-code-index:tracedecay-code-index__+eval-helpers_+lang-dockerfile_+test-helpers__dev", "//crates/tracedecay-contracts:tracedecay-contracts__dev", "//crates/tracedecay-domain:tracedecay-domain__dev", "//crates/tracedecay-graph-db:tracedecay-graph-db__+eval-helpers_+test-helpers__dev", "//crates/tracedecay-private-fs:tracedecay-private-fs__+test-helpers__dev", "//crates/tracedecay-runtime-core:tracedecay-runtime-core__+test-helpers_+test-transport__dev", "//crates/tracedecay-temporal-query:tracedecay-temporal-query__+test-helpers__dev"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -156,7 +158,8 @@ rust_binary( srcs = glob(["src/**/*.rs"]), crate_root = "src/bin/tracedecay_search_bench.rs", edition = crate_edition(), - compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True), + compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) + + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-query:tracedecay-query", "//crates/tracedecay-code-index:tracedecay-code-index__bare__d5f4099fe25", "//crates/tracedecay-contracts:tracedecay-contracts", "//crates/tracedecay-domain:tracedecay-domain", "//crates/tracedecay-graph-db:tracedecay-graph-db", "//crates/tracedecay-private-fs:tracedecay-private-fs", "//crates/tracedecay-runtime-core:tracedecay-runtime-core", "//crates/tracedecay-temporal-query:tracedecay-temporal-query"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -173,7 +176,8 @@ rust_binary( srcs = glob(["src/**/*.rs"]), crate_root = "src/bin/tracedecay_search_bench.rs", edition = crate_edition(), - compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True), + compile_data = glob(["src/**/*"], exclude=["src/**/*.rs"], allow_empty = True) + + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], version = "0.1.0", deps = all_crate_deps(normal = True) + ["//crates/tracedecay-query:tracedecay-query__+production_+test-helpers__dev", "//crates/tracedecay-code-index:tracedecay-code-index__+eval-helpers_+lang-dockerfile_+test-helpers__dev", "//crates/tracedecay-contracts:tracedecay-contracts__dev", "//crates/tracedecay-domain:tracedecay-domain__dev", "//crates/tracedecay-graph-db:tracedecay-graph-db__+eval-helpers_+test-helpers__dev", "//crates/tracedecay-private-fs:tracedecay-private-fs__+test-helpers__dev", "//crates/tracedecay-runtime-core:tracedecay-runtime-core__+test-helpers_+test-transport__dev", "//crates/tracedecay-temporal-query:tracedecay-temporal-query__+test-helpers__dev"], proc_macro_deps = all_crate_deps(proc_macro = True), @@ -282,7 +286,8 @@ rust_test( proc_macro_deps = all_crate_deps(proc_macro = True, proc_macro_dev = True), aliases = aliases(normal = True, normal_dev = True), rustc_flags = perf_rustc_flags(1), - compile_data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True), + compile_data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + + ["//evals/agent_adoption:fixture", "//tests:fixtures_context_eval_project_src_auth", "//tests:fixtures_context_eval_project_src_net", "//tests:fixtures_context_eval_project_src_storage", "//tests:fixtures_search_quality", "//tests:fixtures_search_quality_corpus_cargo-slot_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src", "//tests:fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", "//tests:shared_fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) diff --git a/crates/tracedecay-query/assets/runtime-root/evals/agent_adoption/fixture/Cargo.lock b/crates/tracedecay-query/assets/runtime-root/evals/agent_adoption/fixture/Cargo.lock deleted file mode 100644 index eb4a1fe284..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/evals/agent_adoption/fixture/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "orders_fixture" -version = "0.0.0" diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/auth/login.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/auth/login.rs deleted file mode 100644 index 695569c048..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/auth/login.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Credential checking for interactive login. - -use crate::auth::session::{Session, create_session}; - -/// Checks a username/password pair and, on success, creates a new -/// authenticated session. This is the entry point for "how does -/// authentication work" style questions. -pub fn authenticate(username: &str, password: &str) -> Result { - if !credentials_are_valid(username, password) { - return Err("invalid username or password".to_string()); - } - Ok(create_session(username)) -} - -/// Very small stand-in for a real credential check: non-empty username and -/// a password of at least 8 characters. -fn credentials_are_valid(username: &str, password: &str) -> bool { - !username.is_empty() && password.len() >= 8 -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/auth/session.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/auth/session.rs deleted file mode 100644 index 2d15af2288..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/auth/session.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Session lifecycle: creating sessions after login and validating session -//! tokens on later requests. - -/// A logged-in user's session: a bearer token and the user it belongs to. -pub struct Session { - pub username: String, - pub token: String, - pub expires_at_secs: u64, -} - -/// Creates a new session for `username` with a freshly generated token. -/// Called by `auth::login::authenticate` after credentials are checked. -pub fn create_session(username: &str) -> Session { - Session { - username: username.to_string(), - token: generate_token(username), - expires_at_secs: 3600, - } -} - -/// Checks whether `session` is still within its validity window. Used on -/// every authenticated request to reject expired tokens. -pub fn validate_session(session: &Session, now_secs: u64) -> bool { - now_secs < session.expires_at_secs -} - -fn generate_token(username: &str) -> String { - format!("tok_{username}_{}", username.len()) -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/net/retry.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/net/retry.rs deleted file mode 100644 index 889a1c8cb4..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/net/retry.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Retry-with-backoff logic used by the HTTP client for transient network -//! failures. This is the entry point for "where is the retry logic" -//! style questions. - -/// Configuration for how many attempts to make and how long to wait -/// between them. -pub struct RetryPolicy { - pub max_attempts: u32, - pub base_delay_ms: u64, -} - -impl RetryPolicy { - pub fn new(max_attempts: u32, base_delay_ms: u64) -> Self { - RetryPolicy { - max_attempts, - base_delay_ms, - } - } - - /// Exponential backoff delay before attempt number `attempt` (0-based). - pub fn delay_for_attempt(&self, attempt: u32) -> u64 { - self.base_delay_ms * 2u64.pow(attempt) - } -} - -/// Retries `operation` up to `policy.max_attempts` times, doubling the -/// delay between attempts, until it succeeds or attempts are exhausted. -pub fn retry_with_backoff( - policy: &RetryPolicy, - mut operation: impl FnMut() -> Result, -) -> Result { - let mut attempt = 0; - loop { - match operation() { - Ok(value) => return Ok(value), - Err(err) => { - attempt += 1; - if attempt >= policy.max_attempts { - return Err(err); - } - let _delay = policy.delay_for_attempt(attempt); - } - } - } -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/storage/config_store.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/storage/config_store.rs deleted file mode 100644 index 800d358f9e..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/context_eval_project/src/storage/config_store.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Disk-backed configuration storage: reads and writes a config file. - -use std::collections::HashMap; - -use crate::storage::KeyValueStore; - -/// Reads and writes configuration key/value pairs to a file on disk. -pub struct ConfigStore { - path: String, - values: HashMap, -} - -impl ConfigStore { - pub fn new(path: &str) -> Self { - ConfigStore { - path: path.to_string(), - values: HashMap::new(), - } - } - - /// Reads the full config file contents from disk. - pub fn read_config(&self) -> Result { - std::fs::read_to_string(&self.path).map_err(|err| err.to_string()) - } - - /// Writes a single key/value pair to the config file on disk. This is - /// the entry point for "who writes to the config file" style questions. - pub fn write_config(&mut self, key: &str, value: &str) -> Result<(), String> { - self.write_value(key, value); - std::fs::write(&self.path, format!("{key}={value}\n")).map_err(|err| err.to_string()) - } -} - -impl KeyValueStore for ConfigStore { - fn write_value(&mut self, key: &str, value: &str) { - self.values.insert(key.to_string(), value.to_string()); - } - - fn read_value(&self, key: &str) -> Option { - self.values.get(key).cloned() - } -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/sample.dockerfile b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/sample.dockerfile deleted file mode 100644 index 7c85b20b09..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/sample.dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# Stage 1: Build -FROM rust:1.78-slim AS builder - -ARG APP_VERSION=1.0.0 -ENV CARGO_HOME=/usr/local/cargo - -WORKDIR /app - -COPY Cargo.toml Cargo.lock ./ -COPY src/ ./src/ - -RUN apt-get update && apt-get install -y pkg-config libssl-dev \ - && cargo build --release - -EXPOSE 8080 - -# Stage 2: Runtime -FROM debian:bookworm-slim AS runtime - -LABEL maintainer="dev@example.com" -LABEL version="${APP_VERSION}" - -ENV APP_PORT=8080 -ENV LOG_LEVEL=info - -RUN apt-get update && apt-get install -y ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=builder /app/target/release/myapp /usr/local/bin/myapp - -HEALTHCHECK --interval=30s --timeout=3s \ - CMD curl -f http://localhost:8080/health || exit 1 - -USER nobody - -ENTRYPOINT ["myapp"] -CMD ["--port", "8080"] diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/cargo-slot/src/main.rust.fixture b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/cargo-slot/src/main.rust.fixture deleted file mode 100644 index f89ad50f6d..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/cargo-slot/src/main.rust.fixture +++ /dev/null @@ -1,6 +0,0 @@ -use std::path::{Path, PathBuf}; - -// Sanitized fixture preserving the authorized cargo-slot symbol contract. -fn tmpfs_scratch_path(slot: &Path) -> PathBuf { - PathBuf::from(slot) -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/integration.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/integration.rs deleted file mode 100644 index 098f3bd1e7..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/integration.rs +++ /dev/null @@ -1,652 +0,0 @@ -//! Host-neutral integration catalog contracts. -//! -//! This module records only the PR6 capability model and fixture-backed host -//! availability. Host artifact rendering, lifecycle operations, remote -//! transport, and host-local durable state belong to later delivery slices. - -use std::collections::BTreeSet; - -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use thiserror::Error; - -use crate::{CapabilityId, DomainError, canonical_json_bytes}; - -pub const HOST_INTEGRATION_CATALOG_SCHEMA_VERSION_V1: u16 = 1; -const OBSERVATION_CAPTURE_CAPABILITY_ID: &str = "capability.integration.observation.capture"; - -/// Canonical stock host surfaces shared by catalog, packaging, delivery, and -/// conformance consumers. A host surface is not itself evidence that the PR6 -/// observation-capture capability is fixture-backed. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum HostKindV1 { - ClaudeCode, - CursorDesktop, - CursorCloud, - Codex, - Hermes, - Kiro, - ClineFamily, - Cline, - RooCode, - Kilo, - KimiCode, - OpenCode, -} - -impl HostKindV1 { - pub const ALL: [Self; 12] = [ - Self::ClaudeCode, - Self::CursorDesktop, - Self::CursorCloud, - Self::Codex, - Self::Hermes, - Self::Kiro, - Self::ClineFamily, - Self::Cline, - Self::RooCode, - Self::Kilo, - Self::KimiCode, - Self::OpenCode, - ]; - - /// Project a stock host surface into the bounded PR6 observation catalog - /// only when a checked-in native event fixture proves that integration. - pub const fn fixture_backed_observation_integration_id(self) -> Option { - match self { - Self::ClaudeCode => Some(HostIntegrationIdV1::Claude), - Self::CursorDesktop => Some(HostIntegrationIdV1::Cursor), - Self::Codex => Some(HostIntegrationIdV1::Codex), - Self::Hermes => Some(HostIntegrationIdV1::Hermes), - Self::Kiro => Some(HostIntegrationIdV1::Kiro), - Self::CursorCloud - | Self::ClineFamily - | Self::Cline - | Self::RooCode - | Self::Kilo - | Self::KimiCode - | Self::OpenCode => None, - } - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "snake_case")] -pub enum HostCapabilityV1 { - Lsp, - NativeDiagnostics, - Hooks, - Mcp, - Cli, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum HostCapabilityUnavailableReasonV1 { - HostApiAbsent, - HostRegistrationUnsupported, - NativeFixtureLimited, - CheckedInEvidenceMissing, - CompetingExtensionClaim, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(tag = "state", content = "reason", rename_all = "snake_case")] -pub enum HostCapabilityStateV1 { - Supported, - Degraded(HostCapabilityUnavailableReasonV1), - Unavailable(HostCapabilityUnavailableReasonV1), -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct HostCapabilityRecordV1 { - pub capability: HostCapabilityV1, - pub state: HostCapabilityStateV1, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct StockHostCapabilityViewV1 { - host: HostKindV1, - capabilities: [HostCapabilityRecordV1; 5], -} - -impl StockHostCapabilityViewV1 { - pub const fn host(&self) -> HostKindV1 { - self.host - } - - pub const fn capabilities(&self) -> &[HostCapabilityRecordV1; 5] { - &self.capabilities - } -} - -const fn canonical_stock_host_capabilities(host: HostKindV1) -> [HostCapabilityRecordV1; 5] { - use HostCapabilityStateV1::{Degraded, Supported, Unavailable}; - use HostCapabilityUnavailableReasonV1::{ - CheckedInEvidenceMissing, HostApiAbsent, HostRegistrationUnsupported, NativeFixtureLimited, - }; - use HostCapabilityV1::{Cli, Hooks, Lsp, Mcp, NativeDiagnostics}; - - let (lsp, native_diagnostics, hooks) = match host { - HostKindV1::ClaudeCode => (Supported, Unavailable(HostApiAbsent), Supported), - HostKindV1::CursorDesktop => ( - Unavailable(HostRegistrationUnsupported), - Supported, - Supported, - ), - HostKindV1::CursorCloud => ( - Unavailable(HostRegistrationUnsupported), - Unavailable(HostApiAbsent), - Unavailable(CheckedInEvidenceMissing), - ), - HostKindV1::Codex | HostKindV1::Hermes => ( - Unavailable(HostRegistrationUnsupported), - Unavailable(HostApiAbsent), - Supported, - ), - HostKindV1::Kiro => ( - Unavailable(HostRegistrationUnsupported), - Unavailable(HostApiAbsent), - Degraded(NativeFixtureLimited), - ), - HostKindV1::ClineFamily | HostKindV1::Cline | HostKindV1::RooCode | HostKindV1::Kilo => ( - Unavailable(HostRegistrationUnsupported), - Unavailable(HostApiAbsent), - Unavailable(HostApiAbsent), - ), - HostKindV1::KimiCode => ( - Unavailable(HostRegistrationUnsupported), - Unavailable(HostApiAbsent), - Supported, - ), - HostKindV1::OpenCode => (Supported, Supported, Supported), - }; - [ - HostCapabilityRecordV1 { - capability: Lsp, - state: lsp, - }, - HostCapabilityRecordV1 { - capability: NativeDiagnostics, - state: native_diagnostics, - }, - HostCapabilityRecordV1 { - capability: Hooks, - state: hooks, - }, - HostCapabilityRecordV1 { - capability: Mcp, - state: Supported, - }, - HostCapabilityRecordV1 { - capability: Cli, - state: Supported, - }, - ] -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum HostIntegrationIdV1 { - /// Claude Code's provider identifier remains `claude` for compatibility. - Claude, - Codex, - Cursor, - Hermes, - Kiro, -} - -impl HostIntegrationIdV1 { - pub const ALL: [Self; 5] = [ - Self::Claude, - Self::Codex, - Self::Cursor, - Self::Hermes, - Self::Kiro, - ]; - - pub const fn as_str(self) -> &'static str { - match self { - Self::Claude => "claude", - Self::Codex => "codex", - Self::Cursor => "cursor", - Self::Hermes => "hermes", - Self::Kiro => "kiro", - } - } - - /// Stable host identifier used by hook, daemon, and telemetry adapters. - pub const fn as_wire(self) -> &'static str { - self.as_str() - } - - /// Stable host identifier used by analytics dimensions. - pub const fn as_key(self) -> &'static str { - self.as_str() - } - - pub fn from_wire(value: &str) -> Option { - match value { - "claude" => Some(Self::Claude), - "codex" => Some(Self::Codex), - "cursor" => Some(Self::Cursor), - "hermes" => Some(Self::Hermes), - "kiro" => Some(Self::Kiro), - _ => None, - } - } - - /// Marker used to debounce this host's incremental project syncs. - pub const fn sync_marker_file(self) -> &'static str { - match self { - Self::Claude => ".claude_post_tool_sync_at", - Self::Codex => ".codex_shell_sync_at", - Self::Cursor => ".cursor_shell_sync_at", - Self::Hermes => ".hermes_terminal_receipt_at", - Self::Kiro => ".kiro_post_tool_sync_at", - } - } -} - -/// Every host integration, including every Hermes profile, binds this one -/// user-owned TraceDecay profile. Hosts never select storage or memory scope. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum TraceDecayProfileBindingV1 { - User, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum IntegrationEffectClassV1 { - DaemonWrite, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum IntegrationPrivacyClassV1 { - SensitiveInputSanitizedByDaemon, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum IntegrationDaemonApiV1 { - HostAdmission, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum IntegrationDaemonActionV1 { - CaptureObservation, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct IntegrationDaemonRequirementV1 { - api: IntegrationDaemonApiV1, - action: IntegrationDaemonActionV1, -} - -impl IntegrationDaemonRequirementV1 { - pub const fn new(api: IntegrationDaemonApiV1, action: IntegrationDaemonActionV1) -> Self { - Self { api, action } - } - - pub const fn api(&self) -> IntegrationDaemonApiV1 { - self.api - } - - pub const fn action(&self) -> IntegrationDaemonActionV1 { - self.action - } -} - -/// Bounded reasons proven by the PR6 native host-event fixtures. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum HostCapabilityReasonV1 { - SpoolRecordTooLarge, - AuthorityUnavailable, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] -#[serde( - tag = "status", - content = "reason_code", - rename_all = "snake_case", - deny_unknown_fields -)] -#[non_exhaustive] -pub enum HostCapabilityAvailabilityV1 { - Supported, - Degraded(HostCapabilityReasonV1), - Unavailable(HostCapabilityReasonV1), -} - -impl HostCapabilityAvailabilityV1 { - /// Legal outcomes proven by every PR6 native host-event fixture. - pub const FIXTURE_BACKED_STATES: [Self; 3] = [ - Self::Supported, - Self::Degraded(HostCapabilityReasonV1::SpoolRecordTooLarge), - Self::Unavailable(HostCapabilityReasonV1::AuthorityUnavailable), - ]; -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct HostCapabilityViewV1 { - integration_id: HostIntegrationIdV1, - profile_binding: TraceDecayProfileBindingV1, - availability_states: Vec, -} - -impl HostCapabilityViewV1 { - pub fn new( - integration_id: HostIntegrationIdV1, - profile_binding: TraceDecayProfileBindingV1, - availability_states: Vec, - ) -> Self { - Self { - integration_id, - profile_binding, - availability_states, - } - } - - pub const fn integration_id(&self) -> HostIntegrationIdV1 { - self.integration_id - } - - pub const fn profile_binding(&self) -> TraceDecayProfileBindingV1 { - self.profile_binding - } - - pub fn availability_states(&self) -> &[HostCapabilityAvailabilityV1] { - &self.availability_states - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct IntegrationCapabilityV1 { - capability_id: CapabilityId, - effect_class: IntegrationEffectClassV1, - privacy_class: IntegrationPrivacyClassV1, - required_daemon: IntegrationDaemonRequirementV1, - hosts: Vec, -} - -impl IntegrationCapabilityV1 { - pub fn capability_id(&self) -> &CapabilityId { - &self.capability_id - } - - pub const fn effect_class(&self) -> IntegrationEffectClassV1 { - self.effect_class - } - - pub const fn privacy_class(&self) -> IntegrationPrivacyClassV1 { - self.privacy_class - } - - pub const fn required_daemon(&self) -> &IntegrationDaemonRequirementV1 { - &self.required_daemon - } - - pub fn hosts(&self) -> &[HostCapabilityViewV1] { - &self.hosts - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct HostIntegrationCatalogV1 { - schema_version: u16, - capabilities: Vec, -} - -impl HostIntegrationCatalogV1 { - pub const fn schema_version(&self) -> u16 { - self.schema_version - } - - pub fn capabilities(&self) -> &[IntegrationCapabilityV1] { - &self.capabilities - } - - pub fn stock_host_capabilities(&self, host: HostKindV1) -> &[HostCapabilityRecordV1; 5] { - match host { - HostKindV1::ClaudeCode => &STOCK_HOST_CAPABILITIES[0], - HostKindV1::CursorDesktop => &STOCK_HOST_CAPABILITIES[1], - HostKindV1::CursorCloud => &STOCK_HOST_CAPABILITIES[2], - HostKindV1::Codex => &STOCK_HOST_CAPABILITIES[3], - HostKindV1::Hermes => &STOCK_HOST_CAPABILITIES[4], - HostKindV1::Kiro => &STOCK_HOST_CAPABILITIES[5], - HostKindV1::ClineFamily => &STOCK_HOST_CAPABILITIES[6], - HostKindV1::Cline => &STOCK_HOST_CAPABILITIES[7], - HostKindV1::RooCode => &STOCK_HOST_CAPABILITIES[8], - HostKindV1::Kilo => &STOCK_HOST_CAPABILITIES[9], - HostKindV1::KimiCode => &STOCK_HOST_CAPABILITIES[10], - HostKindV1::OpenCode => &STOCK_HOST_CAPABILITIES[11], - } - } - - pub fn stock_host_capability_views(&self) -> Vec { - HostKindV1::ALL - .into_iter() - .map(|host| StockHostCapabilityViewV1 { - host, - capabilities: *self.stock_host_capabilities(host), - }) - .collect() - } - - /// Canonical bytes for the complete catalog authority: the bounded PR6 - /// observation fixture matrix plus every stock host surface capability. - pub fn canonical_authority_bytes(&self) -> Result, DomainError> { - canonical_json_bytes(&HostIntegrationCatalogAuthorityPayloadV1 { - observation_catalog: self, - stock_hosts: self.stock_host_capability_views(), - }) - } - - pub fn canonical_authority_digest(&self) -> Result<[u8; 32], DomainError> { - self.canonical_authority_bytes() - .map(|bytes| Sha256::digest(bytes).into()) - } - - /// Canonical per-host projection pinned into embedded bundle manifests. - pub fn host_capability_digest(&self, host: HostKindV1) -> Result<[u8; 32], DomainError> { - canonical_json_bytes(&StockHostCapabilityViewV1 { - host, - capabilities: *self.stock_host_capabilities(host), - }) - .map(|bytes| Sha256::digest(bytes).into()) - } - - pub fn validate(&self) -> Result<(), IntegrationCatalogError> { - if self.schema_version != HOST_INTEGRATION_CATALOG_SCHEMA_VERSION_V1 { - return Err(IntegrationCatalogError::UnsupportedSchemaVersion( - self.schema_version, - )); - } - if self.capabilities.is_empty() { - return Err(IntegrationCatalogError::EmptyCatalog); - } - - let required_states = HostCapabilityAvailabilityV1::FIXTURE_BACKED_STATES - .into_iter() - .collect::>(); - let required_hosts = HostIntegrationIdV1::ALL - .into_iter() - .collect::>(); - let mut capability_ids = BTreeSet::new(); - for capability in &self.capabilities { - capability.capability_id.validate().map_err(|_| { - IntegrationCatalogError::InvalidCapabilityId( - capability.capability_id.as_str().to_owned(), - ) - })?; - if !capability_ids.insert(capability.capability_id.as_str()) { - return Err(IntegrationCatalogError::DuplicateCapabilityId( - capability.capability_id.as_str().to_owned(), - )); - } - if capability.hosts.is_empty() { - return Err(IntegrationCatalogError::EmptyHostMatrix( - capability.capability_id.as_str().to_owned(), - )); - } - - let mut integration_ids = BTreeSet::new(); - for host in &capability.hosts { - if !integration_ids.insert(host.integration_id) { - return Err(IntegrationCatalogError::DuplicateHostIntegration { - capability_id: capability.capability_id.as_str().to_owned(), - integration_id: host.integration_id, - }); - } - - let mut availability_states = BTreeSet::new(); - for state in &host.availability_states { - if !availability_states.insert(*state) { - return Err(IntegrationCatalogError::DuplicateHostAvailabilityState { - capability_id: capability.capability_id.as_str().to_owned(), - integration_id: host.integration_id, - state: *state, - }); - } - } - if availability_states != required_states { - return Err(IntegrationCatalogError::InvalidHostAvailabilityStates { - capability_id: capability.capability_id.as_str().to_owned(), - integration_id: host.integration_id, - missing: required_states - .difference(&availability_states) - .copied() - .collect(), - unexpected: availability_states - .difference(&required_states) - .copied() - .collect(), - }); - } - } - if integration_ids != required_hosts { - return Err(IntegrationCatalogError::IncompleteHostMatrix { - capability_id: capability.capability_id.as_str().to_owned(), - missing: required_hosts - .difference(&integration_ids) - .copied() - .collect(), - }); - } - } - Ok(()) - } -} - -const STOCK_HOST_CAPABILITIES: [[HostCapabilityRecordV1; 5]; 12] = [ - canonical_stock_host_capabilities(HostKindV1::ClaudeCode), - canonical_stock_host_capabilities(HostKindV1::CursorDesktop), - canonical_stock_host_capabilities(HostKindV1::CursorCloud), - canonical_stock_host_capabilities(HostKindV1::Codex), - canonical_stock_host_capabilities(HostKindV1::Hermes), - canonical_stock_host_capabilities(HostKindV1::Kiro), - canonical_stock_host_capabilities(HostKindV1::ClineFamily), - canonical_stock_host_capabilities(HostKindV1::Cline), - canonical_stock_host_capabilities(HostKindV1::RooCode), - canonical_stock_host_capabilities(HostKindV1::Kilo), - canonical_stock_host_capabilities(HostKindV1::KimiCode), - canonical_stock_host_capabilities(HostKindV1::OpenCode), -]; - -#[derive(Serialize)] -struct HostIntegrationCatalogAuthorityPayloadV1<'a> { - observation_catalog: &'a HostIntegrationCatalogV1, - stock_hosts: Vec, -} - -pub fn stock_host_capabilities(host: HostKindV1) -> [HostCapabilityRecordV1; 5] { - *host_integration_catalog_v1().stock_host_capabilities(host) -} - -#[derive(Clone, Debug, Error, PartialEq, Eq)] -#[non_exhaustive] -pub enum IntegrationCatalogError { - #[error("unsupported host integration catalog schema version {0}")] - UnsupportedSchemaVersion(u16), - #[error("host integration catalog must define at least one capability")] - EmptyCatalog, - #[error("invalid capability id `{0}`")] - InvalidCapabilityId(String), - #[error("duplicate capability id `{0}`")] - DuplicateCapabilityId(String), - #[error("capability `{0}` must define a host matrix")] - EmptyHostMatrix(String), - #[error("capability `{capability_id}` repeats host integration `{integration_id:?}`")] - DuplicateHostIntegration { - capability_id: String, - integration_id: HostIntegrationIdV1, - }, - #[error("capability `{capability_id}` omits required host integrations {missing:?}")] - IncompleteHostMatrix { - capability_id: String, - missing: Vec, - }, - #[error( - "capability `{capability_id}` repeats availability state {state:?} for host integration `{integration_id:?}`" - )] - DuplicateHostAvailabilityState { - capability_id: String, - integration_id: HostIntegrationIdV1, - state: HostCapabilityAvailabilityV1, - }, - #[error( - "capability `{capability_id}` has invalid availability states for host integration `{integration_id:?}` (missing {missing:?}, unexpected {unexpected:?})" - )] - InvalidHostAvailabilityStates { - capability_id: String, - integration_id: HostIntegrationIdV1, - missing: Vec, - unexpected: Vec, - }, -} - -pub fn host_integration_catalog_v1() -> HostIntegrationCatalogV1 { - let hosts = HostIntegrationIdV1::ALL - .into_iter() - .map(|integration_id| { - HostCapabilityViewV1::new( - integration_id, - TraceDecayProfileBindingV1::User, - HostCapabilityAvailabilityV1::FIXTURE_BACKED_STATES.to_vec(), - ) - }) - .collect(); - let capability = IntegrationCapabilityV1 { - capability_id: CapabilityId::new(OBSERVATION_CAPTURE_CAPABILITY_ID) - .expect("built-in integration capability id is valid"), - effect_class: IntegrationEffectClassV1::DaemonWrite, - privacy_class: IntegrationPrivacyClassV1::SensitiveInputSanitizedByDaemon, - required_daemon: IntegrationDaemonRequirementV1::new( - IntegrationDaemonApiV1::HostAdmission, - IntegrationDaemonActionV1::CaptureObservation, - ), - hosts, - }; - HostIntegrationCatalogV1 { - schema_version: HOST_INTEGRATION_CATALOG_SCHEMA_VERSION_V1, - capabilities: vec![capability], - } -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/repository.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/repository.rs deleted file mode 100644 index 816ce1a55f..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/repository.rs +++ /dev/null @@ -1,639 +0,0 @@ -//! Pure repository-provenance contracts. -//! -//! Capture happens outside this crate. These values preserve only canonical, -//! path-safe evidence and never infer facts that the capture boundary could -//! not establish. - -use serde::{Deserialize, Deserializer, Serialize}; - -use crate::observation::CanonicalObservationIdV1; -use crate::research::{ - CommitId, DomainError, PrivacyDomainBoundLocatorDigest, ProjectId, ProjectionGenerationId, - RefId, RepositoryCaptureId, RepositoryId, TreeId, UtcMicros, WorktreeId, canonical_sha256, -}; - -const CAPTURE_ID_NAMESPACE: &str = "repository.capture.v1"; - -/// Explicit availability of one repository evidence value. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde( - tag = "availability", - content = "value", - rename_all = "snake_case", - deny_unknown_fields -)] -pub enum EvidenceAvailabilityV1 { - Known(T), - Missing, - Unborn, - Detached, - Conflicted, - PartiallyReadable(T), - Unsupported, - Unavailable, - #[default] - Unknown, -} - -impl EvidenceAvailabilityV1 { - pub fn value(&self) -> Option<&T> { - match self { - Self::Known(value) | Self::PartiallyReadable(value) => Some(value), - Self::Missing - | Self::Unborn - | Self::Detached - | Self::Conflicted - | Self::Unsupported - | Self::Unavailable - | Self::Unknown => None, - } - } - - fn validate_with( - &self, - validate: impl FnOnce(&T) -> Result<(), DomainError>, - ) -> Result<(), DomainError> { - match self.value() { - Some(value) => validate(value), - None => Ok(()), - } - } -} - -/// Repository working-state evidence when it was observable. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum RepositoryDirtyStateV1 { - Clean, - Dirty, - Conflicted, -} - -/// Privacy-safe identity of the configured primary repository remote. -/// -/// The captured digest never contains a URL, credential, query, or fragment. -/// `Missing`, `Invalid`, and `Oversized` stay distinct so callers do not infer -/// that a remote was available when the bounded probe could not retain one. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde( - tag = "availability", - content = "value", - rename_all = "snake_case", - deny_unknown_fields -)] -pub enum RepositoryRemoteIdentityV1 { - Known(PrivacyDomainBoundLocatorDigest), - Missing, - Invalid, - Oversized, - Unavailable, - #[default] - Unknown, -} - -impl RepositoryRemoteIdentityV1 { - pub fn digest(&self) -> Option<&PrivacyDomainBoundLocatorDigest> { - match self { - Self::Known(digest) => Some(digest), - Self::Missing | Self::Invalid | Self::Oversized | Self::Unavailable | Self::Unknown => { - None - } - } - } - - pub const fn is_unknown(&self) -> bool { - matches!(self, Self::Unknown) - } - - fn validate(&self) -> Result<(), DomainError> { - self.digest() - .map_or(Ok(()), PrivacyDomainBoundLocatorDigest::validate) - } -} - -/// Bounded repository facts captured by the daemon/application boundary. -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct RepositoryEvidenceV1 { - attached_ref: EvidenceAvailabilityV1, - head_commit: EvidenceAvailabilityV1, - index_tree: EvidenceAvailabilityV1, - path_identity_digest: EvidenceAvailabilityV1, - #[serde( - default, - skip_serializing_if = "RepositoryRemoteIdentityV1::is_unknown" - )] - remote_identity: RepositoryRemoteIdentityV1, - dirty_state: EvidenceAvailabilityV1, -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -struct RepositoryEvidenceWireV1 { - attached_ref: EvidenceAvailabilityV1, - head_commit: EvidenceAvailabilityV1, - index_tree: EvidenceAvailabilityV1, - path_identity_digest: EvidenceAvailabilityV1, - #[serde(default)] - remote_identity: RepositoryRemoteIdentityV1, - dirty_state: EvidenceAvailabilityV1, -} - -impl<'de> Deserialize<'de> for RepositoryEvidenceV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let wire = RepositoryEvidenceWireV1::deserialize(deserializer)?; - Self::new( - wire.attached_ref, - wire.head_commit, - wire.index_tree, - wire.path_identity_digest, - wire.remote_identity, - wire.dirty_state, - ) - .map_err(serde::de::Error::custom) - } -} - -impl RepositoryEvidenceV1 { - pub fn new( - attached_ref: EvidenceAvailabilityV1, - head_commit: EvidenceAvailabilityV1, - index_tree: EvidenceAvailabilityV1, - path_identity_digest: EvidenceAvailabilityV1, - remote_identity: RepositoryRemoteIdentityV1, - dirty_state: EvidenceAvailabilityV1, - ) -> Result { - let evidence = Self { - attached_ref, - head_commit, - index_tree, - path_identity_digest, - remote_identity, - dirty_state, - }; - evidence.validate()?; - Ok(evidence) - } - - pub fn attached_ref(&self) -> &EvidenceAvailabilityV1 { - &self.attached_ref - } - - pub fn head_commit(&self) -> &EvidenceAvailabilityV1 { - &self.head_commit - } - - pub fn index_tree(&self) -> &EvidenceAvailabilityV1 { - &self.index_tree - } - - pub fn path_identity_digest(&self) -> &EvidenceAvailabilityV1 { - &self.path_identity_digest - } - - pub fn remote_identity(&self) -> &RepositoryRemoteIdentityV1 { - &self.remote_identity - } - - pub fn dirty_state(&self) -> &EvidenceAvailabilityV1 { - &self.dirty_state - } - - pub fn validate(&self) -> Result<(), DomainError> { - self.attached_ref.validate_with(RefId::validate)?; - self.head_commit.validate_with(|value| { - value.validate()?; - validate_git_object_id(value.as_str(), "HEAD commit") - })?; - self.index_tree.validate_with(|value| { - value.validate()?; - validate_git_object_id(value.as_str(), "index tree") - })?; - self.path_identity_digest - .validate_with(PrivacyDomainBoundLocatorDigest::validate)?; - self.remote_identity.validate()?; - Ok(()) - } -} - -/// One immutable, path-safe capture of repository identity and state. -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct RepositoryProvenanceV1 { - capture_id: RepositoryCaptureId, - repository_id: RepositoryId, - #[serde(default, skip_serializing_if = "Option::is_none")] - project_id: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - worktree_id: Option, - canonical_root_digest: PrivacyDomainBoundLocatorDigest, - evidence: RepositoryEvidenceV1, - captured_at: UtcMicros, -} - -impl RepositoryProvenanceV1 { - pub fn new( - repository_id: RepositoryId, - project_id: Option, - worktree_id: Option, - canonical_root_digest: PrivacyDomainBoundLocatorDigest, - evidence: RepositoryEvidenceV1, - captured_at: UtcMicros, - ) -> Result { - repository_id.validate()?; - if let Some(project_id) = &project_id { - project_id.validate()?; - } - if let Some(worktree_id) = &worktree_id { - worktree_id.validate()?; - } - canonical_root_digest.validate()?; - evidence.validate()?; - - let capture_id = derive_capture_id( - &repository_id, - project_id.as_ref(), - worktree_id.as_ref(), - &canonical_root_digest, - &evidence, - captured_at, - )?; - Ok(Self { - capture_id, - repository_id, - project_id, - worktree_id, - canonical_root_digest, - evidence, - captured_at, - }) - } - - pub fn capture_id(&self) -> &RepositoryCaptureId { - &self.capture_id - } - - pub fn repository_id(&self) -> &RepositoryId { - &self.repository_id - } - - pub fn project_id(&self) -> Option<&ProjectId> { - self.project_id.as_ref() - } - - pub fn worktree_id(&self) -> Option<&WorktreeId> { - self.worktree_id.as_ref() - } - - pub fn canonical_root_digest(&self) -> &PrivacyDomainBoundLocatorDigest { - &self.canonical_root_digest - } - - pub fn evidence(&self) -> &RepositoryEvidenceV1 { - &self.evidence - } - - pub fn captured_at(&self) -> UtcMicros { - self.captured_at - } - - pub fn validate(&self) -> Result<(), DomainError> { - let expected = derive_capture_id( - &self.repository_id, - self.project_id.as_ref(), - self.worktree_id.as_ref(), - &self.canonical_root_digest, - &self.evidence, - self.captured_at, - )?; - if self.capture_id != expected { - return Err(DomainError::SnapshotMismatch { - field: "repository capture identity", - }); - } - Ok(()) - } -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -struct RepositoryProvenanceWireV1 { - capture_id: RepositoryCaptureId, - repository_id: RepositoryId, - #[serde(default)] - project_id: Option, - #[serde(default)] - worktree_id: Option, - canonical_root_digest: PrivacyDomainBoundLocatorDigest, - evidence: RepositoryEvidenceV1, - captured_at: UtcMicros, -} - -impl<'de> Deserialize<'de> for RepositoryProvenanceV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let wire = RepositoryProvenanceWireV1::deserialize(deserializer)?; - let capture = Self::new( - wire.repository_id, - wire.project_id, - wire.worktree_id, - wire.canonical_root_digest, - wire.evidence, - wire.captured_at, - ) - .map_err(serde::de::Error::custom)?; - if wire.capture_id != capture.capture_id { - return Err(serde::de::Error::custom( - "repository capture identity does not match canonical evidence", - )); - } - Ok(capture) - } -} - -/// Repository capture pinned to one immutable projection generation. -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct GenerationBoundRepositoryProvenanceV1 { - generation_id: ProjectionGenerationId, - capture_id: RepositoryCaptureId, - capture: RepositoryProvenanceV1, - #[serde(default, skip_serializing_if = "Option::is_none")] - source_observation: Option, -} - -impl GenerationBoundRepositoryProvenanceV1 { - pub fn new( - generation_id: ProjectionGenerationId, - capture: RepositoryProvenanceV1, - source_observation: Option, - ) -> Result { - generation_id.validate()?; - capture.validate()?; - Ok(Self { - generation_id, - capture_id: capture.capture_id.clone(), - capture, - source_observation, - }) - } - - pub fn generation_id(&self) -> &ProjectionGenerationId { - &self.generation_id - } - - pub fn capture_id(&self) -> &RepositoryCaptureId { - &self.capture_id - } - - pub fn capture(&self) -> &RepositoryProvenanceV1 { - &self.capture - } - - pub fn source_observation(&self) -> Option<&CanonicalObservationIdV1> { - self.source_observation.as_ref() - } - - pub fn validate(&self) -> Result<(), DomainError> { - self.generation_id.validate()?; - self.capture.validate()?; - if self.capture_id != self.capture.capture_id { - return Err(DomainError::SnapshotMismatch { - field: "generation repository capture identity", - }); - } - Ok(()) - } -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -struct GenerationBoundRepositoryProvenanceWireV1 { - generation_id: ProjectionGenerationId, - capture_id: RepositoryCaptureId, - capture: RepositoryProvenanceV1, - #[serde(default)] - source_observation: Option, -} - -impl<'de> Deserialize<'de> for GenerationBoundRepositoryProvenanceV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let wire = GenerationBoundRepositoryProvenanceWireV1::deserialize(deserializer)?; - let binding = Self::new(wire.generation_id, wire.capture, wire.source_observation) - .map_err(serde::de::Error::custom)?; - if wire.capture_id != binding.capture_id { - return Err(serde::de::Error::custom( - "generation binding capture identity does not match its capture", - )); - } - Ok(binding) - } -} - -#[derive(Serialize)] -struct RepositoryCaptureIdentityMaterialV1<'a> { - repository_id: &'a RepositoryId, - project_id: Option<&'a ProjectId>, - worktree_id: Option<&'a WorktreeId>, - canonical_root_digest: &'a PrivacyDomainBoundLocatorDigest, - evidence: &'a RepositoryEvidenceV1, - captured_at: UtcMicros, -} - -fn derive_capture_id( - repository_id: &RepositoryId, - project_id: Option<&ProjectId>, - worktree_id: Option<&WorktreeId>, - canonical_root_digest: &PrivacyDomainBoundLocatorDigest, - evidence: &RepositoryEvidenceV1, - captured_at: UtcMicros, -) -> Result { - let digest = canonical_sha256(&( - CAPTURE_ID_NAMESPACE, - RepositoryCaptureIdentityMaterialV1 { - repository_id, - project_id, - worktree_id, - canonical_root_digest, - evidence, - captured_at, - }, - ))?; - let encoded = digest - .as_str() - .strip_prefix("sha256:") - .ok_or(DomainError::NonCanonical { - field: "repository capture identity digest", - })?; - RepositoryCaptureId::new(format!("{CAPTURE_ID_NAMESPACE}.{encoded}")) -} - -fn validate_git_object_id(value: &str, field: &'static str) -> Result<(), DomainError> { - if matches!(value.len(), 40 | 64) - && value - .bytes() - .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) - { - Ok(()) - } else { - Err(DomainError::NonCanonical { field }) - } -} - -#[cfg(test)] -mod tests { - use serde_json::Value; - - use super::*; - - const DIGEST_A: &str = - "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - const DIGEST_B: &str = - "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; - const COMMIT: &str = "0123456789abcdef0123456789abcdef01234567"; - const TREE: &str = "89abcdef0123456789abcdef0123456789abcdef"; - - fn id(value: &str) -> T - where - T: TryFrom, - { - T::try_from(value.to_owned()).expect("valid fixture identity") - } - - fn evidence() -> RepositoryEvidenceV1 { - RepositoryEvidenceV1::new( - EvidenceAvailabilityV1::Known(id("refs/heads/main")), - EvidenceAvailabilityV1::Known(id(COMMIT)), - EvidenceAvailabilityV1::Known(id(TREE)), - EvidenceAvailabilityV1::Known(id(DIGEST_B)), - RepositoryRemoteIdentityV1::Known(id(DIGEST_A)), - EvidenceAvailabilityV1::Known(RepositoryDirtyStateV1::Clean), - ) - .unwrap() - } - - fn capture() -> RepositoryProvenanceV1 { - RepositoryProvenanceV1::new( - id("repository.fixture"), - Some(id("project.fixture")), - Some(id("worktree.fixture")), - id(DIGEST_A), - evidence(), - UtcMicros(42), - ) - .unwrap() - } - - #[test] - fn capture_identity_is_deterministic() { - assert_eq!(capture().capture_id(), capture().capture_id()); - } - - #[test] - fn detached_unborn_and_unavailable_are_preserved() { - let evidence = RepositoryEvidenceV1::new( - EvidenceAvailabilityV1::Detached, - EvidenceAvailabilityV1::Unborn, - EvidenceAvailabilityV1::Unavailable, - EvidenceAvailabilityV1::Unknown, - RepositoryRemoteIdentityV1::Unknown, - EvidenceAvailabilityV1::Unknown, - ) - .unwrap(); - let round_trip: RepositoryEvidenceV1 = - serde_json::from_value(serde_json::to_value(&evidence).unwrap()).unwrap(); - - assert_eq!(round_trip.attached_ref(), &EvidenceAvailabilityV1::Detached); - assert_eq!(round_trip.head_commit(), &EvidenceAvailabilityV1::Unborn); - assert_eq!( - round_trip.index_tree(), - &EvidenceAvailabilityV1::Unavailable - ); - assert_eq!( - round_trip.remote_identity(), - &RepositoryRemoteIdentityV1::Unknown - ); - } - - #[test] - fn legacy_unknown_remote_identity_preserves_capture_identity() { - let legacy_evidence = RepositoryEvidenceV1::new( - EvidenceAvailabilityV1::Known(id("refs/heads/main")), - EvidenceAvailabilityV1::Known(id(COMMIT)), - EvidenceAvailabilityV1::Known(id(TREE)), - EvidenceAvailabilityV1::Known(id(DIGEST_B)), - RepositoryRemoteIdentityV1::Unknown, - EvidenceAvailabilityV1::Known(RepositoryDirtyStateV1::Clean), - ) - .unwrap(); - let capture = RepositoryProvenanceV1::new( - id("repository.legacy-fixture"), - Some(id("project.fixture")), - Some(id("worktree.fixture")), - id(DIGEST_A), - legacy_evidence, - UtcMicros(42), - ) - .unwrap(); - let encoded = serde_json::to_value(&capture).unwrap(); - assert!(encoded["evidence"].get("remote_identity").is_none()); - let decoded: RepositoryProvenanceV1 = serde_json::from_value(encoded).unwrap(); - assert_eq!(decoded.capture_id(), capture.capture_id()); - assert_eq!( - decoded.evidence().remote_identity(), - &RepositoryRemoteIdentityV1::Unknown - ); - } - - #[test] - fn standalone_evidence_deserialization_rejects_noncanonical_git_object_ids() { - let mut value = serde_json::to_value(evidence()).unwrap(); - value["head_commit"]["value"] = Value::String("0123456789abcdef".into()); - - assert!(serde_json::from_value::(value).is_err()); - } - - #[test] - fn project_and_worktree_aliases_do_not_define_repository_identity() { - let first = capture(); - let second = RepositoryProvenanceV1::new( - first.repository_id().clone(), - Some(id("project.alias")), - Some(id("worktree.alias")), - first.canonical_root_digest().clone(), - first.evidence().clone(), - first.captured_at(), - ) - .unwrap(); - - assert_eq!(first.repository_id(), second.repository_id()); - assert_ne!(first.capture_id(), second.capture_id()); - } - - #[test] - fn generation_binding_rejects_capture_mismatch_and_tampering() { - let binding = GenerationBoundRepositoryProvenanceV1::new( - id("projection.fixture.v1"), - capture(), - None, - ) - .unwrap(); - let mut mismatched = serde_json::to_value(&binding).unwrap(); - mismatched["capture_id"] = Value::String("repository.capture.v1.invalid".into()); - assert!( - serde_json::from_value::(mismatched).is_err() - ); - - let mut tampered = serde_json::to_value(&binding).unwrap(); - tampered["capture"]["captured_at"] = Value::from(43); - assert!(serde_json::from_value::(tampered).is_err()); - } -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/canonical.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/canonical.rs deleted file mode 100644 index dafc4ad16b..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/canonical.rs +++ /dev/null @@ -1,158 +0,0 @@ -use serde::Serialize; -use serde_json::Value; -use sha2::{Digest, Sha256}; - -use super::error::DomainError; -use super::id::ManifestDigest; - -/// Serialize any domain value to the crate's canonical JSON byte form. -pub fn canonical_json_bytes(value: &T) -> Result, DomainError> { - let value = serde_json::to_value(value) - .map_err(|error| DomainError::CanonicalSerialization(error.to_string()))?; - let mut output = Vec::new(); - write_canonical(&value, &mut output)?; - Ok(output) -} - -/// Serialize a JSON value with recursively lexicographic object keys and no -/// insignificant whitespace. -pub fn canonical_json_value(value: &Value) -> Result { - let mut output = String::new(); - write_canonical(value, &mut output)?; - Ok(output) -} - -/// Compute the canonical SHA-256 digest encoding used by domain manifests. -pub fn canonical_sha256(value: &T) -> Result { - let value = serde_json::to_value(value) - .map_err(|error| DomainError::CanonicalSerialization(error.to_string()))?; - let mut hasher = Sha256::new(); - write_canonical(&value, &mut hasher)?; - let digest = hasher.finalize(); - let mut encoded = String::with_capacity("sha256:".len() + digest.len() * 2); - encoded.push_str("sha256:"); - for byte in digest { - use std::fmt::Write as _; - write!(&mut encoded, "{byte:02x}") - .map_err(|error| DomainError::CanonicalSerialization(error.to_string()))?; - } - ManifestDigest::new(encoded) -} - -trait CanonicalSink { - fn write(&mut self, chunk: &str); -} - -impl CanonicalSink for String { - fn write(&mut self, chunk: &str) { - self.push_str(chunk); - } -} - -impl CanonicalSink for Vec { - fn write(&mut self, chunk: &str) { - self.extend_from_slice(chunk.as_bytes()); - } -} - -impl CanonicalSink for Sha256 { - fn write(&mut self, chunk: &str) { - Digest::update(self, chunk.as_bytes()); - } -} - -fn write_canonical(value: &Value, output: &mut impl CanonicalSink) -> Result<(), DomainError> { - match value { - Value::Null => output.write("null"), - Value::Bool(value) => output.write(if *value { "true" } else { "false" }), - Value::Number(value) => output.write(&value.to_string()), - Value::String(value) => output.write( - &serde_json::to_string(value) - .map_err(|error| DomainError::CanonicalSerialization(error.to_string()))?, - ), - Value::Array(values) => { - output.write("["); - for (index, value) in values.iter().enumerate() { - if index > 0 { - output.write(","); - } - write_canonical(value, output)?; - } - output.write("]"); - } - Value::Object(values) => { - output.write("{"); - let mut entries: Vec<_> = values.iter().collect(); - entries.sort_unstable_by_key(|(key, _)| *key); - for (index, (key, value)) in entries.into_iter().enumerate() { - if index > 0 { - output.write(","); - } - output.write( - &serde_json::to_string(key) - .map_err(|error| DomainError::CanonicalSerialization(error.to_string()))?, - ); - output.write(":"); - write_canonical(value, output)?; - } - output.write("}"); - } - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - #[test] - fn canonical_outputs_match_for_nested_ordering_and_scalars() { - let value = json!({ - "z": null, - "array": [true, false, -12.5, 0, {"z": 2, "a": 1}], - "a": {"z": "last", "a": "first"}, - }); - let expected = concat!( - r#"{"a":{"a":"first","z":"last"},"#, - r#""array":[true,false,-12.5,0,{"a":1,"z":2}],"#, - r#""z":null}"#, - ); - - let text = canonical_json_value(&value).unwrap(); - let bytes = canonical_json_bytes(&value).unwrap(); - - assert_eq!(text, expected); - assert_eq!(bytes, expected.as_bytes()); - } - - #[test] - fn canonical_outputs_preserve_json_escapes_and_unicode() { - let value = json!({ - "unicode": "雪😀é", - "escaped": "quote: \" slash: \\ newline:\n tab:\t control:\u{0001}", - }); - let expected = "{\"escaped\":\"quote: \\\" slash: \\\\ newline:\\n tab:\\t control:\\u0001\",\"unicode\":\"雪😀é\"}"; - - assert_eq!(canonical_json_value(&value).unwrap(), expected); - assert_eq!(canonical_json_bytes(&value).unwrap(), expected.as_bytes()); - } - - #[test] - fn streaming_digest_matches_digest_of_canonical_bytes() { - let value = json!({ - "unicode": ["雪", "😀", "é"], - "nested": {"z": null, "a": [true, "line\nfeed", 42]}, - }); - let bytes = canonical_json_bytes(&value).unwrap(); - let digest = Sha256::digest(&bytes); - let mut expected = String::from("sha256:"); - for byte in digest { - use std::fmt::Write as _; - write!(&mut expected, "{byte:02x}").unwrap(); - } - - assert_eq!(canonical_sha256(&value).unwrap().as_str(), expected); - } -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/coverage.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/coverage.rs deleted file mode 100644 index 32f4494ca0..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/coverage.rs +++ /dev/null @@ -1,672 +0,0 @@ -use std::collections::BTreeMap; -use std::fmt; -use std::marker::PhantomData; -use std::ops::{Index, IndexMut}; - -use serde::de::{IgnoredAny, SeqAccess, Visitor}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use super::error::DomainError; -use super::id::{ - AuthorityEpoch, BrainId, BrainNodeId, EntityVersionId, ManifestDigest, ShardId, - StoreAuthorityId, ensure_unique, validate_canonical_string, -}; -use super::time::UtcMicros; -use super::watermark::{ShardWatermark, VectorWatermark}; - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum ShardDispositionV1 { - Searched, - Skipped, - Stale, - Unavailable, - Incompatible, - Locked, - Redacted, - Truncated, -} - -/// Whether the complete shard universe was known when coverage was captured. -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum CoverageUniverseKnowledgeV1 { - Known, - #[default] - Unknown, -} - -/// Registry-owned retention class code. The domain records the code without -/// implementing retention policy or storage behavior. -#[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(transparent)] -pub struct RetentionClass(String); - -impl RetentionClass { - pub fn new(value: impl Into) -> Result { - let value = value.into(); - validate_canonical_string(&value, "RetentionClass")?; - Ok(Self(value)) - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl<'de> Deserialize<'de> for RetentionClass { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct EvidenceRetentionWatermark { - pub evaluated_at: UtcMicros, - pub cutoffs: BTreeMap, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum BrainNodeRoleV1 { - Standalone, - Authority, - RemoteClient, - ReadReplica, - Standby, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case", deny_unknown_fields)] -pub enum ReadConsistencyV1 { - Authoritative, - BoundedStale { max_lag_micros: u64 }, - OfflineCache, -} - -/// Signed cache-grant state plus the authority-side evidence verified for this -/// coverage evaluation. -/// -/// The grant digest identifies the immutable signed snapshot. Its validity and -/// purge frontier are carried directly so freshness cannot be inferred from an -/// unbound cache timestamp. Optional verified fields represent current evidence; -/// absent evidence never implies current placement, authority, or revocation. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct VerifiedCacheGrantSnapshotV1 { - pub grant_digest: ManifestDigest, - pub issued_at: UtcMicros, - pub not_after: UtcMicros, - pub grant_revocation_generation: u64, - pub purge_frontier: VectorWatermark, - pub verified_placement_version: Option, - pub verified_authority_id: Option, - pub verified_authority_epoch: Option, - pub verified_revocation_generation: Option, - pub verified_purge_frontier: Option, -} - -impl VerifiedCacheGrantSnapshotV1 { - fn validate(&self) -> Result<(), DomainError> { - self.grant_digest.validate()?; - if self.not_after <= self.issued_at { - return Err(DomainError::NonCanonical { - field: "cache grant validity", - }); - } - for shard in self.purge_frontier.components.keys() { - shard.validate()?; - } - if let Some(verified_purge_frontier) = &self.verified_purge_frontier { - for shard in verified_purge_frontier.components.keys() { - shard.validate()?; - } - } - if let Some(placement_version) = &self.verified_placement_version { - placement_version.validate()?; - } - if let Some(authority_id) = &self.verified_authority_id { - authority_id.validate()?; - } - if self.verified_authority_id.is_some() != self.verified_authority_epoch.is_some() { - return Err(DomainError::UnknownReference { - field: "cache grant verified authority", - }); - } - Ok(()) - } - - fn proves_current_access( - &self, - evaluated_at: UtcMicros, - cache_not_after: UtcMicros, - placement_version: &EntityVersionId, - authority_id: &StoreAuthorityId, - authority_epoch: AuthorityEpoch, - ) -> bool { - self.issued_at <= evaluated_at - && self.not_after == cache_not_after - && self.not_after > evaluated_at - && self.verified_placement_version.as_ref() == Some(placement_version) - && self.verified_authority_id.as_ref() == Some(authority_id) - && self.verified_authority_epoch == Some(authority_epoch) - && self.verified_revocation_generation == Some(self.grant_revocation_generation) - && self - .verified_purge_frontier - .as_ref() - .is_some_and(|verified| verified.dominates(&self.purge_frontier)) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct RemoteShardCoverageV1 { - pub shard_id: ShardId, - pub authority_id: StoreAuthorityId, - pub authority_epoch: AuthorityEpoch, - pub served_by_node: BrainNodeId, - pub served_by_role: BrainNodeRoleV1, - pub captured_watermark: Option, - pub cache_generation: Option, - pub cache_not_after: Option, - pub cache_age_micros: Option, - pub cache_grant_snapshot: Option, - pub sync_lag_micros: Option, - pub pending_local_observations: u64, - pub pending_tombstone_acks: u64, -} - -impl RemoteShardCoverageV1 { - fn validate(&self) -> Result<(), DomainError> { - self.shard_id.validate()?; - self.authority_id.validate()?; - self.served_by_node.validate()?; - if let Some(watermark) = &self.captured_watermark - && watermark.shard_id != self.shard_id - { - return Err(DomainError::UnknownReference { - field: "remote coverage watermark shard", - }); - } - let cache_field_count = [ - self.cache_generation.is_some(), - self.cache_not_after.is_some(), - self.cache_age_micros.is_some(), - self.cache_grant_snapshot.is_some(), - ] - .into_iter() - .filter(|present| *present) - .count(); - if cache_field_count != 0 && cache_field_count != 4 { - return Err(DomainError::UnknownReference { - field: "remote coverage cache state", - }); - } - if let Some(cache_grant_snapshot) = &self.cache_grant_snapshot { - cache_grant_snapshot.validate()?; - } - Ok(()) - } - - fn has_fresh_cache_at( - &self, - evaluated_at: UtcMicros, - placement_version: &EntityVersionId, - ) -> bool { - self.cache_generation.is_some() - && self.cache_age_micros.is_some() - && self.cache_not_after.is_some_and(|cache_not_after| { - self.cache_grant_snapshot.as_ref().is_some_and(|snapshot| { - snapshot.proves_current_access( - evaluated_at, - cache_not_after, - placement_version, - &self.authority_id, - self.authority_epoch, - ) - }) - }) - } - - fn is_authoritatively_complete(&self) -> bool { - self.served_by_role == BrainNodeRoleV1::Authority - && self.captured_watermark.is_some() - && self.pending_local_observations == 0 - && self.pending_tombstone_acks == 0 - } -} - -/// A vector whose length cannot exceed `MAX`. -/// -/// Deserialization consumes at most `MAX` values into memory. If another value -/// is present, it is ignored and the sequence is rejected immediately. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BoundedVec(Vec); - -impl BoundedVec { - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - pub fn iter(&self) -> std::slice::Iter<'_, T> { - self.0.iter() - } - - pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> { - self.0.iter_mut() - } -} - -impl TryFrom> for BoundedVec { - type Error = Vec; - - fn try_from(values: Vec) -> Result { - if values.len() <= MAX { - Ok(Self(values)) - } else { - Err(values) - } - } -} - -impl Index for BoundedVec { - type Output = T; - - fn index(&self, index: usize) -> &Self::Output { - &self.0[index] - } -} - -impl IndexMut for BoundedVec { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.0[index] - } -} - -impl<'a, T, const MAX: usize> IntoIterator for &'a BoundedVec { - type Item = &'a T; - type IntoIter = std::slice::Iter<'a, T>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, T, const MAX: usize> IntoIterator for &'a mut BoundedVec { - type Item = &'a mut T; - type IntoIter = std::slice::IterMut<'a, T>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut() - } -} - -impl IntoIterator for BoundedVec { - type Item = T; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl Serialize for BoundedVec -where - T: Serialize, -{ - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.0.serialize(serializer) - } -} - -struct BoundedVecVisitor(PhantomData); - -impl<'de, T, const MAX: usize> Visitor<'de> for BoundedVecVisitor -where - T: Deserialize<'de>, -{ - type Value = BoundedVec; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "a sequence with at most {MAX} elements") - } - - fn visit_seq(self, mut sequence: A) -> Result - where - A: SeqAccess<'de>, - { - let mut values = Vec::with_capacity(sequence.size_hint().unwrap_or(0).min(MAX)); - while values.len() < MAX { - match sequence.next_element()? { - Some(value) => values.push(value), - None => return Ok(BoundedVec(values)), - } - } - if sequence.next_element::()?.is_some() { - return Err(serde::de::Error::invalid_length( - MAX.saturating_add(1), - &self, - )); - } - Ok(BoundedVec(values)) - } -} - -impl<'de, T, const MAX: usize> Deserialize<'de> for BoundedVec -where - T: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_seq(BoundedVecVisitor(PhantomData)) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct RemoteCoverageV1 { - pub brain_id: BrainId, - pub placement_version: EntityVersionId, - /// Immutable instant at which this coverage decision was evaluated. - pub evaluated_at: UtcMicros, - pub requested_consistency: ReadConsistencyV1, - pub shards: BoundedVec, -} - -impl RemoteCoverageV1 { - fn validate(&self) -> Result<(), DomainError> { - self.brain_id.validate()?; - self.placement_version.validate()?; - ensure_unique( - self.shards.iter().map(|shard| &shard.shard_id), - "remote coverage shards", - )?; - for shard in &self.shards { - shard.validate()?; - } - Ok(()) - } - - fn is_complete_for_requested_consistency(&self) -> bool { - match self.requested_consistency { - ReadConsistencyV1::Authoritative => self - .shards - .iter() - .all(RemoteShardCoverageV1::is_authoritatively_complete), - ReadConsistencyV1::BoundedStale { max_lag_micros } => self.shards.iter().all(|shard| { - shard.pending_tombstone_acks == 0 - && shard - .sync_lag_micros - .is_some_and(|lag| lag <= max_lag_micros) - }), - ReadConsistencyV1::OfflineCache => self.shards.iter().all(|shard| { - shard.has_fresh_cache_at(self.evaluated_at, &self.placement_version) - && shard.pending_tombstone_acks == 0 - }), - } - } -} - -/// Exact per-shard disposition captured with a retrieval result. -/// -/// The in-memory authority is one map: a shard cannot occupy two disposition -/// groups. Custom serde preserves the grouped-vector V1 fixture wire form. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct CoverageReportV1 { - pub dispositions: BTreeMap, - pub freshness: BTreeMap, - pub retention_watermark: Option, - pub universe: CoverageUniverseKnowledgeV1, - pub remote: Option, -} - -impl CoverageReportV1 { - pub fn validate(&self) -> Result<(), DomainError> { - for shard in self.dispositions.keys() { - shard.validate()?; - } - for (shard, watermark) in &self.freshness { - shard.validate()?; - if !self.dispositions.contains_key(shard) || shard != &watermark.shard_id { - return Err(DomainError::UnknownReference { - field: "coverage freshness shard", - }); - } - } - if let Some(retention) = &self.retention_watermark { - for class in retention.cutoffs.keys() { - validate_canonical_string(class.as_str(), "RetentionClass")?; - } - } - if let Some(remote) = &self.remote { - remote.validate()?; - if remote - .shards - .iter() - .any(|shard| !self.dispositions.contains_key(&shard.shard_id)) - { - return Err(DomainError::UnknownReference { - field: "remote coverage disposition shard", - }); - } - } - Ok(()) - } - - pub fn is_complete(&self) -> bool { - if self.universe == CoverageUniverseKnowledgeV1::Unknown - || self.dispositions.is_empty() - || self - .dispositions - .values() - .any(|disposition| *disposition != ShardDispositionV1::Searched) - { - return false; - } - self.remote - .as_ref() - .is_none_or(RemoteCoverageV1::is_complete_for_requested_consistency) - } - - pub fn disposition(&self, shard: &ShardId) -> Option { - self.dispositions.get(shard).copied() - } -} - -#[derive(Default, Deserialize)] -#[serde(deny_unknown_fields)] -struct CoverageWireV1 { - #[serde(default)] - searched: Vec, - #[serde(default)] - skipped: Vec, - #[serde(default)] - stale: Vec, - #[serde(default)] - unavailable: Vec, - #[serde(default)] - incompatible: Vec, - #[serde(default)] - locked: Vec, - #[serde(default)] - redacted: Vec, - #[serde(default)] - truncated: Vec, - #[serde(default)] - freshness: BTreeMap, - #[serde(default, skip_serializing_if = "Option::is_none")] - retention_watermark: Option, - #[serde(default)] - unknown_coverage: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - remote: Option, -} - -#[derive(Serialize)] -#[serde(deny_unknown_fields)] -struct CoverageWireRefV1<'a> { - #[serde(default)] - searched: Vec<&'a ShardId>, - #[serde(default)] - skipped: Vec<&'a ShardId>, - #[serde(default)] - stale: Vec<&'a ShardId>, - #[serde(default)] - unavailable: Vec<&'a ShardId>, - #[serde(default)] - incompatible: Vec<&'a ShardId>, - #[serde(default)] - locked: Vec<&'a ShardId>, - #[serde(default)] - redacted: Vec<&'a ShardId>, - #[serde(default)] - truncated: Vec<&'a ShardId>, - #[serde(default)] - freshness: &'a BTreeMap, - #[serde(default, skip_serializing_if = "Option::is_none")] - retention_watermark: Option<&'a EvidenceRetentionWatermark>, - #[serde(default)] - unknown_coverage: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - remote: Option<&'a RemoteCoverageV1>, -} - -impl CoverageWireV1 { - fn add_group( - dispositions: &mut BTreeMap, - shards: Vec, - disposition: ShardDispositionV1, - ) -> Result<(), DomainError> { - for shard in shards { - if dispositions.insert(shard, disposition).is_some() { - return Err(DomainError::DuplicateId { - field: "coverage dispositions", - }); - } - } - Ok(()) - } -} - -impl Serialize for CoverageReportV1 { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut searched = Vec::new(); - let mut skipped = Vec::new(); - let mut stale = Vec::new(); - let mut unavailable = Vec::new(); - let mut incompatible = Vec::new(); - let mut locked = Vec::new(); - let mut redacted = Vec::new(); - let mut truncated = Vec::new(); - for (shard, disposition) in &self.dispositions { - let group = match disposition { - ShardDispositionV1::Searched => &mut searched, - ShardDispositionV1::Skipped => &mut skipped, - ShardDispositionV1::Stale => &mut stale, - ShardDispositionV1::Unavailable => &mut unavailable, - ShardDispositionV1::Incompatible => &mut incompatible, - ShardDispositionV1::Locked => &mut locked, - ShardDispositionV1::Redacted => &mut redacted, - ShardDispositionV1::Truncated => &mut truncated, - }; - group.push(shard); - } - CoverageWireRefV1 { - searched, - skipped, - stale, - unavailable, - incompatible, - locked, - redacted, - truncated, - freshness: &self.freshness, - retention_watermark: self.retention_watermark.as_ref(), - unknown_coverage: Some(self.universe == CoverageUniverseKnowledgeV1::Unknown), - remote: self.remote.as_ref(), - } - .serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for CoverageReportV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let wire = CoverageWireV1::deserialize(deserializer)?; - let mut dispositions = BTreeMap::new(); - CoverageWireV1::add_group( - &mut dispositions, - wire.searched, - ShardDispositionV1::Searched, - ) - .and_then(|_| { - CoverageWireV1::add_group(&mut dispositions, wire.skipped, ShardDispositionV1::Skipped) - }) - .and_then(|_| { - CoverageWireV1::add_group(&mut dispositions, wire.stale, ShardDispositionV1::Stale) - }) - .and_then(|_| { - CoverageWireV1::add_group( - &mut dispositions, - wire.unavailable, - ShardDispositionV1::Unavailable, - ) - }) - .and_then(|_| { - CoverageWireV1::add_group( - &mut dispositions, - wire.incompatible, - ShardDispositionV1::Incompatible, - ) - }) - .and_then(|_| { - CoverageWireV1::add_group(&mut dispositions, wire.locked, ShardDispositionV1::Locked) - }) - .and_then(|_| { - CoverageWireV1::add_group( - &mut dispositions, - wire.redacted, - ShardDispositionV1::Redacted, - ) - }) - .and_then(|_| { - CoverageWireV1::add_group( - &mut dispositions, - wire.truncated, - ShardDispositionV1::Truncated, - ) - }) - .map_err(serde::de::Error::custom)?; - - let report = Self { - dispositions, - freshness: wire.freshness, - retention_watermark: wire.retention_watermark, - universe: match wire.unknown_coverage { - Some(false) => CoverageUniverseKnowledgeV1::Known, - Some(true) | None => CoverageUniverseKnowledgeV1::Unknown, - }, - remote: wire.remote, - }; - report.validate().map_err(serde::de::Error::custom)?; - Ok(report) - } -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/error.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/error.rs deleted file mode 100644 index 83725c9367..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/error.rs +++ /dev/null @@ -1,38 +0,0 @@ -use thiserror::Error; - -/// Validation failures for pure research-domain values. -#[derive(Clone, Debug, Error, PartialEq, Eq)] -pub enum DomainError { - #[error("{field} must not be empty")] - Empty { field: &'static str }, - #[error("{field} is not canonical")] - NonCanonical { field: &'static str }, - #[error("{field} contains a duplicate identity")] - DuplicateId { field: &'static str }, - #[error("{field} references an unknown identity")] - UnknownReference { field: &'static str }, - #[error("{field} is not pinned to the required snapshot")] - SnapshotMismatch { field: &'static str }, - #[error("confidence must be finite and within [0.0, 1.0]")] - InvalidConfidence, - #[error("{field} violates the structural bounds for sanitized text")] - UnsafeText { field: &'static str }, - #[error("an Activity primary subject cannot also have related_activity")] - ActivityFacetOnActivitySubject, - #[error("a manifest cannot supersede itself")] - SelfSupersession, - #[error("direct authorship requires provider-linked activity evidence")] - AuthorshipWithoutProviderLinkage, - #[error("time interval start must not be after its end")] - InvalidTimeInterval, - #[error("redacted and rejected counts cannot exceed scanned count")] - InvalidRedactionCounts, - #[error( - "evidence declared by a user or provider, or directly observed, requires confidence 1.0" - )] - NonCertainDeclaration, - #[error("manifest digest does not match its canonical domain-separated payload")] - DigestMismatch, - #[error("canonical serialization failed: {0}")] - CanonicalSerialization(String), -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/time.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/time.rs deleted file mode 100644 index 07e04fd372..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/time.rs +++ /dev/null @@ -1,25 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use super::error::DomainError; - -/// UTC timestamp represented as microseconds from the Unix epoch. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(transparent)] -pub struct UtcMicros(pub i64); - -/// Closed half-open occurrence interval. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(deny_unknown_fields)] -pub struct TimeInterval { - pub start: UtcMicros, - pub end: UtcMicros, -} - -impl TimeInterval { - pub fn validate(&self) -> Result<(), DomainError> { - if self.start > self.end { - return Err(DomainError::InvalidTimeInterval); - } - Ok(()) - } -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/watermark.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/watermark.rs deleted file mode 100644 index 2bcb1b50a7..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/watermark.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::collections::BTreeMap; - -use serde::{Deserialize, Serialize}; - -use super::id::ShardId; - -/// Per-shard progress without a fabricated global sequence. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct VectorWatermark { - pub components: BTreeMap, -} - -impl VectorWatermark { - pub fn dominates(&self, other: &Self) -> bool { - other - .components - .iter() - .all(|(shard, sequence)| self.components.get(shard).copied().unwrap_or(0) >= *sequence) - } - - pub fn partial_cmp_components(&self, other: &Self) -> Option { - let self_dominates = self.dominates(other); - let other_dominates = other.dominates(self); - match (self_dominates, other_dominates) { - (true, true) => Some(std::cmp::Ordering::Equal), - (true, false) => Some(std::cmp::Ordering::Greater), - (false, true) => Some(std::cmp::Ordering::Less), - (false, false) => None, - } - } - - pub fn merge_max(&self, other: &Self) -> Self { - let mut components = self.components.clone(); - for (shard, sequence) in &other.components { - components - .entry(shard.clone()) - .and_modify(|current| *current = (*current).max(*sequence)) - .or_insert(*sequence); - } - Self { components } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(deny_unknown_fields)] -pub struct ShardWatermark { - pub shard_id: ShardId, - pub outbox_sequence: u64, -} diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/session.rs b/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/session.rs deleted file mode 100644 index 4c92397cae..0000000000 --- a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/session.rs +++ /dev/null @@ -1,2114 +0,0 @@ -//! Pure session and temporal-retrieval contracts. -//! -//! These values carry identity, temporal, authority, coverage, and compact -//! context metadata only. Persistence, policy, hydration, and query execution -//! remain outside the domain crate. - -use std::collections::BTreeSet; -use std::fmt::{self, Write}; - -use serde::{Deserialize, Deserializer, Serialize}; -use sha2::{Digest, Sha256}; -use thiserror::Error; - -use crate::observation::{CanonicalMessageRoleV1, CanonicalObservationIdV1}; -use crate::research::{ - AgentInstanceId, ComponentVersion, DataVersionDigest, EvidenceClass, MessageId, ObservationId, - RetrievalAnchorId, SanitizationReceiptRefV1, SessionId, ThreadId, TurnId, UtcMicros, -}; - -const MESSAGE_OCCURRENCE_ID_DOMAIN: &[u8] = b"tracedecay.session.message-occurrence.v1\0"; - -/// Validation failures at the session-domain boundary. -#[derive(Clone, Debug, Error, PartialEq, Eq)] -pub enum SessionContractError { - #[error("{field} is not a canonical identity")] - InvalidIdentity { field: &'static str }, - #[error("{field} must be non-zero")] - ZeroValue { field: &'static str }, - #[error("a byte range must be non-empty and ordered")] - InvalidByteRange, - #[error("message occurrence identity does not match its observation and ordinal")] - OccurrenceIdentityMismatch, - #[error("a logical copy cannot reference itself")] - CopySelfReference, - #[error("copy proof does not identify the copied-from occurrence")] - CopyProofSourceMismatch, - #[error("a temporal assertion cannot relate an anchor to itself")] - AssertionSelfReference, - #[error("a session summary requires at least one exact source anchor")] - SummarySourcesRequired, - #[error("a session summary source anchor is duplicated")] - DuplicateSummarySource, - #[error("a session summary cannot predate its knowledge horizon")] - InvalidSummaryHorizon, - #[error("a session summary cannot name itself as predecessor")] - SummarySelfPredecessor, - #[error("{group} grouping provenance requires a corresponding identity")] - GroupingProvenanceWithoutId { group: &'static str }, - #[error("{group} identity requires grouping provenance")] - GroupingIdWithoutProvenance { group: &'static str }, - #[error("compact context contains a duplicate anchor")] - DuplicateContextAnchor, - #[error("compact context encoded bytes do not match its records")] - CompactContextEncodedBytesMismatch, - #[error("compact context record bytes overflow the aggregate")] - CompactContextEncodedBytesOverflow, - #[error("a temporal coverage interval requires at least one bound")] - EmptyCoverageInterval, - #[error("a temporal coverage interval has reversed bounds")] - ReversedCoverageInterval, - #[error("source coverage intervals are not canonical")] - NonCanonicalCoverageIntervals, - #[error("source coverage frontiers are inconsistent")] - InvalidSourceCoverageFrontiers, - #[error("source coverage state and reason disagree")] - InvalidSourceCoverageState, - #[error("a source coverage receipt requires at least one source")] - SourceCoverageRequired, - #[error("a source coverage receipt contains duplicate sources")] - DuplicateSourceCoverage, - #[error("source coverage does not match the receipt request")] - SourceCoverageRequestMismatch, - #[error("a refresh key requires at least one source target")] - RefreshSourcesRequired, - #[error("a refresh key contains duplicate source targets")] - DuplicateRefreshSource, - #[error("a refresh source target regresses its observed frontier")] - InvalidRefreshSourceFrontier, - #[error("derived evidence requires at least one ordered member")] - DerivedEvidenceMembersRequired, - #[error("derived evidence contains a duplicate occurrence member")] - DuplicateDerivedEvidenceMember, - #[error("derived evidence membership ordinals are not contiguous")] - NoncontiguousDerivedEvidenceOrdinals, - #[error("derived evidence endpoints do not match the ordered manifest")] - DerivedEvidenceEndpointMismatch, - #[error("derived evidence session identity mismatches its members")] - DerivedEvidenceSessionMismatch, - #[error("derived evidence authority must be derived_projection")] - DerivedEvidenceAuthorityMismatch, - #[error("derived evidence member digest does not match membership")] - DerivedEvidenceMemberDigestMismatch, -} - -macro_rules! session_string_id { - ($($name:ident),+ $(,)?) => {$( - #[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] - #[serde(transparent)] - pub struct $name(String); - - impl $name { - pub fn new(value: impl Into) -> Result { - let value = value.into(); - if value.is_empty() - || value.trim() != value - || value.len() > 512 - || value.chars().any(char::is_control) - { - return Err(SessionContractError::InvalidIdentity { - field: stringify!($name), - }); - } - Ok(Self(value)) - } - - pub fn as_str(&self) -> &str { - &self.0 - } - } - - impl<'de> Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Self::new(String::deserialize(deserializer)?) - .map_err(serde::de::Error::custom) - } - } - - impl fmt::Display for $name { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str(&self.0) - } - } - )+}; -} - -session_string_id!( - SessionSummaryIdV1, - TemporalAssertionIdV1, - SessionRefreshOperationIdV1, - SessionCursorKeyIdV1, - SessionSourceIdV1, -); - -/// Stable identity of one projected output from a canonical observation. -#[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(transparent)] -pub struct MessageOccurrenceIdV1(String); - -impl MessageOccurrenceIdV1 { - pub fn derive( - observation_id: &CanonicalObservationIdV1, - output_ordinal: ProjectionOutputOrdinalV1, - ) -> Self { - let mut hasher = Sha256::new(); - hasher.update(MESSAGE_OCCURRENCE_ID_DOMAIN); - hasher.update(observation_id.as_str().as_bytes()); - hasher.update(output_ordinal.value().to_be_bytes()); - let digest = hasher.finalize(); - let mut encoded = String::with_capacity(71); - encoded.push_str("sha256:"); - for byte in digest { - write!(&mut encoded, "{byte:02x}").expect("writing to a String cannot fail"); - } - Self(encoded) - } - - pub fn new(value: impl Into) -> Result { - let value = value.into(); - let valid = value.strip_prefix("sha256:").is_some_and(|encoded| { - encoded.len() == 64 - && encoded - .bytes() - .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) - }); - if !valid { - return Err(SessionContractError::InvalidIdentity { - field: "MessageOccurrenceIdV1", - }); - } - Ok(Self(value)) - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl<'de> Deserialize<'de> for MessageOccurrenceIdV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom) - } -} - -impl fmt::Display for MessageOccurrenceIdV1 { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str(&self.0) - } -} - -/// Zero-based output position within one canonical observation projection. -#[derive( - Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, -)] -#[serde(transparent)] -pub struct ProjectionOutputOrdinalV1(u32); - -impl ProjectionOutputOrdinalV1 { - pub const fn new(value: u32) -> Self { - Self(value) - } - - pub const fn value(self) -> u32 { - self.0 - } -} - -macro_rules! nonzero_numeric_value { - ($name:ident, $integer:ty, $field:literal) => { - #[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] - #[serde(transparent)] - pub struct $name($integer); - - impl $name { - pub fn new(value: $integer) -> Result { - if value == 0 { - return Err(SessionContractError::ZeroValue { field: $field }); - } - Ok(Self(value)) - } - - pub const fn value(self) -> $integer { - self.0 - } - } - - impl<'de> Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Self::new(<$integer>::deserialize(deserializer)?).map_err(serde::de::Error::custom) - } - } - }; -} - -nonzero_numeric_value!( - SessionProjectionGenerationV1, - u64, - "session projection generation" -); -nonzero_numeric_value!(SessionCursorVersionV1, u16, "session cursor version"); - -/// Persisted signing-key reference used by authenticated collection cursors. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(deny_unknown_fields)] -pub struct SignedCursorKeyRefV1 { - pub key_id: SessionCursorKeyIdV1, - pub version: SessionCursorVersionV1, -} - -/// Requested temporal interpretation. -#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] -pub enum TemporalModeV1 { - Current, - AsOf { cutoff: UtcMicros }, - Evolution, - Forensic, -} - -impl TemporalModeV1 { - pub const fn as_str(self) -> &'static str { - match self { - Self::Current => "current", - Self::AsOf { .. } => "as_of", - Self::Evolution => "evolution", - Self::Forensic => "forensic", - } - } -} - -impl<'de> Deserialize<'de> for TemporalModeV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] - enum Wire { - Current {}, - AsOf { cutoff: UtcMicros }, - Evolution {}, - Forensic {}, - } - - Ok(match Wire::deserialize(deserializer)? { - Wire::Current {} => Self::Current, - Wire::AsOf { cutoff } => Self::AsOf { cutoff }, - Wire::Evolution {} => Self::Evolution, - Wire::Forensic {} => Self::Forensic, - }) - } -} - -/// Retrieval unit selected by the caller. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum RetrievalGrainV1 { - Occurrence, - LogicalMessage, - Turn, - Session, - Thread, - Agent, - Summary, - EvidenceSpan, - EvidenceBurst, -} - -impl RetrievalGrainV1 { - pub const fn as_str(self) -> &'static str { - match self { - Self::Occurrence => "occurrence", - Self::LogicalMessage => "logical_message", - Self::Turn => "turn", - Self::Session => "session", - Self::Thread => "thread", - Self::Agent => "agent", - Self::Summary => "summary", - Self::EvidenceSpan => "evidence_span", - Self::EvidenceBurst => "evidence_burst", - } - } -} - -/// Canonical half-open UTF-8 byte range retained by exact retrieval evidence. -#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ByteRangeV1 { - start: u64, - end: u64, -} - -impl ByteRangeV1 { - pub const fn new(start: u64, end: u64) -> Result { - if start >= end { - return Err(SessionContractError::InvalidByteRange); - } - Ok(Self { start, end }) - } - - pub const fn start(self) -> u64 { - self.start - } - - pub const fn end(self) -> u64 { - self.end - } -} - -impl<'de> Deserialize<'de> for ByteRangeV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct SerializedByteRange { - start: u64, - end: u64, - } - - let range = SerializedByteRange::deserialize(deserializer)?; - Self::new(range.start, range.end).map_err(serde::de::Error::custom) - } -} - -/// Valid-time evidence for an occurrence or assertion. -#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] -pub enum TemporalValidityV1 { - Known { valid_at: UtcMicros }, - Unknown, -} - -impl TemporalValidityV1 { - /// Whether evidence may participate in a representative answer for `mode`. - /// - /// `as_of` is intentionally strict: both knowledge and valid time must be - /// at or before the cutoff, and unknown valid time is excluded. - pub const fn is_representative_at(self, knowledge_at: UtcMicros, mode: TemporalModeV1) -> bool { - match mode { - TemporalModeV1::AsOf { cutoff } => match self { - Self::Known { valid_at } => knowledge_at.0 <= cutoff.0 && valid_at.0 <= cutoff.0, - Self::Unknown => false, - }, - TemporalModeV1::Current | TemporalModeV1::Evolution | TemporalModeV1::Forensic => true, - } - } -} - -impl<'de> Deserialize<'de> for TemporalValidityV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] - enum Wire { - Known { valid_at: UtcMicros }, - Unknown {}, - } - - Ok(match Wire::deserialize(deserializer)? { - Wire::Known { valid_at } => Self::Known { valid_at }, - Wire::Unknown {} => Self::Unknown, - }) - } -} - -/// Whether a Turn/thread identity was observed or deterministically projected. -#[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] -pub enum GroupingProvenanceV1 { - ProviderNative, - DerivedRoleBoundary { projector_version: ComponentVersion }, -} - -impl GroupingProvenanceV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - match self { - Self::ProviderNative => Ok(()), - Self::DerivedRoleBoundary { projector_version } => projector_version - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "grouping projector version", - }), - } - } -} - -impl<'de> Deserialize<'de> for GroupingProvenanceV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] - enum Wire { - ProviderNative {}, - DerivedRoleBoundary { projector_version: ComponentVersion }, - } - - Ok(match Wire::deserialize(deserializer)? { - Wire::ProviderNative {} => Self::ProviderNative, - Wire::DerivedRoleBoundary { projector_version } => { - Self::DerivedRoleBoundary { projector_version } - } - }) - } -} - -/// Authority class attached to session evidence. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum SessionAuthorityClassV1 { - ProviderNative, - CanonicalObservation, - ExplicitAnchorAssertion, - DerivedProjection, - ImmutableSummary, -} - -impl SessionAuthorityClassV1 { - pub const fn as_str(self) -> &'static str { - match self { - Self::ProviderNative => "provider_native", - Self::CanonicalObservation => "canonical_observation", - Self::ExplicitAnchorAssertion => "explicit_anchor_assertion", - Self::DerivedProjection => "derived_projection", - Self::ImmutableSummary => "immutable_summary", - } - } -} - -/// Exact evidence and sanitization references behind one session-domain row. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct SessionEvidenceMetadataV1 { - pub authority: SessionAuthorityClassV1, - pub evidence_class: EvidenceClass, - pub source_anchor_id: RetrievalAnchorId, - pub sanitization_receipt: SanitizationReceiptRefV1, -} - -impl SessionEvidenceMetadataV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - self.source_anchor_id - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "session evidence source anchor", - })?; - self.sanitization_receipt - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "session evidence sanitization receipt", - }) - } -} - -/// Immutable projected occurrence of one message-like observation output. -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct MessageOccurrenceRecordV1 { - pub occurrence_id: MessageOccurrenceIdV1, - pub source_observation_id: CanonicalObservationIdV1, - pub projection_output_ordinal: ProjectionOutputOrdinalV1, - pub retrieval_anchor_id: RetrievalAnchorId, - pub session_id: SessionId, - pub thread_id: Option, - pub thread_grouping: Option, - pub turn_id: Option, - pub turn_grouping: Option, - pub message_id: Option, - pub agent_id: Option, - pub role: CanonicalMessageRoleV1, - pub knowledge_at: UtcMicros, - pub valid_time: TemporalValidityV1, - pub evidence: SessionEvidenceMetadataV1, -} - -impl MessageOccurrenceRecordV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - if self.occurrence_id - != MessageOccurrenceIdV1::derive( - &self.source_observation_id, - self.projection_output_ordinal, - ) - { - return Err(SessionContractError::OccurrenceIdentityMismatch); - } - self.retrieval_anchor_id - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "occurrence retrieval anchor", - })?; - self.session_id - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "occurrence session", - })?; - for (field, value) in [ - ( - "occurrence thread", - self.thread_id.as_ref().map(ThreadId::validate), - ), - ( - "occurrence turn", - self.turn_id.as_ref().map(TurnId::validate), - ), - ( - "occurrence message", - self.message_id.as_ref().map(MessageId::validate), - ), - ( - "occurrence agent", - self.agent_id.as_ref().map(AgentInstanceId::validate), - ), - ] { - if value.is_some_and(|result| result.is_err()) { - return Err(SessionContractError::InvalidIdentity { field }); - } - } - for (group, id_present, provenance) in [ - ( - "thread", - self.thread_id.is_some(), - self.thread_grouping.as_ref(), - ), - ("turn", self.turn_id.is_some(), self.turn_grouping.as_ref()), - ] { - match (id_present, provenance) { - (false, Some(_)) => { - return Err(SessionContractError::GroupingProvenanceWithoutId { group }); - } - (true, None) => { - return Err(SessionContractError::GroupingIdWithoutProvenance { group }); - } - (true, Some(provenance)) => provenance.validate()?, - (false, None) => {} - } - } - self.evidence.validate() - } -} - -impl<'de> Deserialize<'de> for MessageOccurrenceRecordV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - occurrence_id: MessageOccurrenceIdV1, - source_observation_id: CanonicalObservationIdV1, - projection_output_ordinal: ProjectionOutputOrdinalV1, - retrieval_anchor_id: RetrievalAnchorId, - session_id: SessionId, - thread_id: Option, - thread_grouping: Option, - turn_id: Option, - turn_grouping: Option, - message_id: Option, - agent_id: Option, - role: CanonicalMessageRoleV1, - knowledge_at: UtcMicros, - valid_time: TemporalValidityV1, - evidence: SessionEvidenceMetadataV1, - } - - let wire = Wire::deserialize(deserializer)?; - let record = Self { - occurrence_id: wire.occurrence_id, - source_observation_id: wire.source_observation_id, - projection_output_ordinal: wire.projection_output_ordinal, - retrieval_anchor_id: wire.retrieval_anchor_id, - session_id: wire.session_id, - thread_id: wire.thread_id, - thread_grouping: wire.thread_grouping, - turn_id: wire.turn_id, - turn_grouping: wire.turn_grouping, - message_id: wire.message_id, - agent_id: wire.agent_id, - role: wire.role, - knowledge_at: wire.knowledge_at, - valid_time: wire.valid_time, - evidence: wire.evidence, - }; - record.validate().map_err(serde::de::Error::custom)?; - Ok(record) - } -} - -/// Evidence that can prove two occurrences are logical copies. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] -pub enum CopyProofV1 { - ProviderLinkage { - source_occurrence_id: MessageOccurrenceIdV1, - provider_record_id: ObservationId, - }, - ParentMessageLinkage { - source_occurrence_id: MessageOccurrenceIdV1, - parent_message_id: MessageId, - }, - ExplicitAnchorAssertion { - source_occurrence_id: MessageOccurrenceIdV1, - assertion_anchor_id: RetrievalAnchorId, - }, -} - -impl CopyProofV1 { - pub fn source_occurrence_id(&self) -> &MessageOccurrenceIdV1 { - match self { - Self::ProviderLinkage { - source_occurrence_id, - .. - } - | Self::ParentMessageLinkage { - source_occurrence_id, - .. - } - | Self::ExplicitAnchorAssertion { - source_occurrence_id, - .. - } => source_occurrence_id, - } - } -} - -/// Immutable evidence-backed logical-copy edge. -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct LogicalCopyRecordV1 { - pub occurrence_id: MessageOccurrenceIdV1, - pub copied_from_occurrence_id: MessageOccurrenceIdV1, - pub proof: CopyProofV1, - /// When the copy edge became visible to the authoritative store/projection. - pub knowledge_at: UtcMicros, - /// Independent represented-world validity; legacy rows default to unknown. - pub valid_time: TemporalValidityV1, -} - -impl LogicalCopyRecordV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - if self.occurrence_id == self.copied_from_occurrence_id { - return Err(SessionContractError::CopySelfReference); - } - if self.proof.source_occurrence_id() != &self.copied_from_occurrence_id { - return Err(SessionContractError::CopyProofSourceMismatch); - } - Ok(()) - } -} - -impl<'de> Deserialize<'de> for LogicalCopyRecordV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - occurrence_id: MessageOccurrenceIdV1, - copied_from_occurrence_id: MessageOccurrenceIdV1, - proof: CopyProofV1, - #[serde(default)] - knowledge_at: Option, - #[serde(default)] - valid_time: Option, - } - - let wire = Wire::deserialize(deserializer)?; - let record = Self { - occurrence_id: wire.occurrence_id, - copied_from_occurrence_id: wire.copied_from_occurrence_id, - proof: wire.proof, - // Legacy copy wires omit bitemporal fields; preserve unknown validity - // and a zero knowledge watermark rather than inventing provider time. - knowledge_at: wire.knowledge_at.unwrap_or(UtcMicros(0)), - valid_time: wire.valid_time.unwrap_or(TemporalValidityV1::Unknown), - }; - record.validate().map_err(serde::de::Error::custom)?; - Ok(record) - } -} - -/// Temporal relationship asserted between two exact anchors. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum TemporalAssertionKindV1 { - Corrects, - Supersedes, - Contradicts, - Supports, -} - -impl TemporalAssertionKindV1 { - pub const fn as_str(self) -> &'static str { - match self { - Self::Corrects => "corrects", - Self::Supersedes => "supersedes", - Self::Contradicts => "contradicts", - Self::Supports => "supports", - } - } -} - -/// Immutable temporal assertion over exact evidence anchors. -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct TemporalAssertionRecordV1 { - pub assertion_id: TemporalAssertionIdV1, - pub kind: TemporalAssertionKindV1, - pub subject_anchor_id: RetrievalAnchorId, - pub object_anchor_id: RetrievalAnchorId, - pub knowledge_at: UtcMicros, - pub valid_time: TemporalValidityV1, - pub evidence: SessionEvidenceMetadataV1, -} - -impl TemporalAssertionRecordV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - if self.subject_anchor_id == self.object_anchor_id { - return Err(SessionContractError::AssertionSelfReference); - } - self.subject_anchor_id - .validate() - .and_then(|_| self.object_anchor_id.validate()) - .map_err(|_| SessionContractError::InvalidIdentity { - field: "temporal assertion anchor", - })?; - self.evidence.validate() - } -} - -impl<'de> Deserialize<'de> for TemporalAssertionRecordV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - assertion_id: TemporalAssertionIdV1, - kind: TemporalAssertionKindV1, - subject_anchor_id: RetrievalAnchorId, - object_anchor_id: RetrievalAnchorId, - knowledge_at: UtcMicros, - valid_time: TemporalValidityV1, - evidence: SessionEvidenceMetadataV1, - } - - let wire = Wire::deserialize(deserializer)?; - let record = Self { - assertion_id: wire.assertion_id, - kind: wire.kind, - subject_anchor_id: wire.subject_anchor_id, - object_anchor_id: wire.object_anchor_id, - knowledge_at: wire.knowledge_at, - valid_time: wire.valid_time, - evidence: wire.evidence, - }; - record.validate().map_err(serde::de::Error::custom)?; - Ok(record) - } -} - -/// Exact source-time horizon covered by an immutable summary. -#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(deny_unknown_fields)] -pub struct SummarySourceHorizonV1 { - pub knowledge_through: UtcMicros, - pub valid_through: Option, -} - -impl SummarySourceHorizonV1 { - pub fn validate(self) -> Result<(), SessionContractError> { - Ok(()) - } -} - -impl<'de> Deserialize<'de> for SummarySourceHorizonV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - knowledge_through: UtcMicros, - #[serde(deserialize_with = "deserialize_required_option")] - valid_through: Option, - } - - fn deserialize_required_option<'de, D, T>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - T: Deserialize<'de>, - { - Option::deserialize(deserializer) - } - - let wire = Wire::deserialize(deserializer)?; - let horizon = Self { - knowledge_through: wire.knowledge_through, - valid_through: wire.valid_through, - }; - horizon.validate().map_err(serde::de::Error::custom)?; - Ok(horizon) - } -} - -/// Publication metadata that binds a summary to its route and sanitization. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct SummaryPublicationMetadataV1 { - pub model_route: ComponentVersion, - pub configuration_digest: DataVersionDigest, - pub sanitization_receipt: SanitizationReceiptRefV1, -} - -impl SummaryPublicationMetadataV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - self.model_route - .validate() - .and_then(|_| self.configuration_digest.validate()) - .and_then(|_| self.sanitization_receipt.validate()) - .map_err(|_| SessionContractError::InvalidIdentity { - field: "summary publication metadata", - }) - } -} - -/// Immutable summary node with exact, identity-unique source anchors. -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct SessionSummaryRecordV1 { - summary_id: SessionSummaryIdV1, - session_id: SessionId, - summary_anchor_id: RetrievalAnchorId, - source_anchors: Vec, - source_horizon: SummarySourceHorizonV1, - created_at: UtcMicros, - predecessor_summary_id: Option, - publication: Option, -} - -impl SessionSummaryRecordV1 { - pub fn new( - summary_id: SessionSummaryIdV1, - session_id: SessionId, - summary_anchor_id: RetrievalAnchorId, - source_anchors: Vec, - source_horizon: SummarySourceHorizonV1, - created_at: UtcMicros, - ) -> Result { - if source_anchors.is_empty() { - return Err(SessionContractError::SummarySourcesRequired); - } - let mut unique = BTreeSet::new(); - if source_anchors - .iter() - .any(|source| !unique.insert(source.clone())) - { - return Err(SessionContractError::DuplicateSummarySource); - } - if created_at < source_horizon.knowledge_through { - return Err(SessionContractError::InvalidSummaryHorizon); - } - source_horizon.validate()?; - session_id - .validate() - .and_then(|_| summary_anchor_id.validate()) - .map_err(|_| SessionContractError::InvalidIdentity { - field: "session summary", - })?; - for source in &source_anchors { - source - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "session summary source anchor", - })?; - } - let source_anchors = unique.into_iter().collect(); - Ok(Self { - summary_id, - session_id, - summary_anchor_id, - source_anchors, - source_horizon, - created_at, - predecessor_summary_id: None, - publication: None, - }) - } - - pub fn with_predecessor( - mut self, - predecessor: SessionSummaryIdV1, - ) -> Result { - if self.summary_id == predecessor { - return Err(SessionContractError::SummarySelfPredecessor); - } - self.predecessor_summary_id = Some(predecessor); - Ok(self) - } - - pub fn with_publication( - mut self, - publication: SummaryPublicationMetadataV1, - ) -> Result { - publication.validate()?; - self.publication = Some(publication); - Ok(self) - } - - pub fn summary_id(&self) -> &SessionSummaryIdV1 { - &self.summary_id - } - - pub fn session_id(&self) -> &SessionId { - &self.session_id - } - - pub fn summary_anchor_id(&self) -> &RetrievalAnchorId { - &self.summary_anchor_id - } - - pub fn source_anchors(&self) -> &[RetrievalAnchorId] { - &self.source_anchors - } - - pub fn source_horizon(&self) -> SummarySourceHorizonV1 { - self.source_horizon - } - - pub fn created_at(&self) -> UtcMicros { - self.created_at - } - - pub fn predecessor_summary_id(&self) -> Option<&SessionSummaryIdV1> { - self.predecessor_summary_id.as_ref() - } - - pub fn publication(&self) -> Option<&SummaryPublicationMetadataV1> { - self.publication.as_ref() - } -} - -impl<'de> Deserialize<'de> for SessionSummaryRecordV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - summary_id: SessionSummaryIdV1, - session_id: SessionId, - summary_anchor_id: RetrievalAnchorId, - source_anchors: Vec, - source_horizon: SummarySourceHorizonV1, - created_at: UtcMicros, - predecessor_summary_id: Option, - publication: Option, - } - - let wire = Wire::deserialize(deserializer)?; - let mut summary = Self::new( - wire.summary_id, - wire.session_id, - wire.summary_anchor_id, - wire.source_anchors, - wire.source_horizon, - wire.created_at, - ) - .map_err(serde::de::Error::custom)?; - if let Some(predecessor) = wire.predecessor_summary_id { - summary = summary - .with_predecessor(predecessor) - .map_err(serde::de::Error::custom)?; - } - if let Some(publication) = wire.publication { - summary = summary - .with_publication(publication) - .map_err(serde::de::Error::custom)?; - } - Ok(summary) - } -} - -/// Current hydration eligibility after authorization and retention rechecks. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum HydrationStateV1 { - Available, - RetainedButUnavailable, - Redacted, - Deleted, - RetentionExpired, - Unauthorized, - Locked, - UnverifiableLegacy, -} - -impl HydrationStateV1 { - pub const fn as_str(self) -> &'static str { - match self { - Self::Available => "available", - Self::RetainedButUnavailable => "retained_but_unavailable", - Self::Redacted => "redacted", - Self::Deleted => "deleted", - Self::RetentionExpired => "retention_expired", - Self::Unauthorized => "unauthorized", - Self::Locked => "locked", - Self::UnverifiableLegacy => "unverifiable_legacy", - } - } -} - -/// Why an otherwise relevant item was omitted from compact context. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum ContextOmissionReasonV1 { - ByteBudget, - TokenBudget, - Unauthorized, - Redacted, - Deleted, - RetentionExpired, - Locked, - Unavailable, - SummaryHorizonMismatch, - DuplicateRepresentative, -} - -impl ContextOmissionReasonV1 { - pub const fn as_str(self) -> &'static str { - match self { - Self::ByteBudget => "byte_budget", - Self::TokenBudget => "token_budget", - Self::Unauthorized => "unauthorized", - Self::Redacted => "redacted", - Self::Deleted => "deleted", - Self::RetentionExpired => "retention_expired", - Self::Locked => "locked", - Self::Unavailable => "unavailable", - Self::SummaryHorizonMismatch => "summary_horizon_mismatch", - Self::DuplicateRepresentative => "duplicate_representative", - } - } -} - -/// One selected context item. Payload text remains behind the exact anchor. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct CompactContextRecordV1 { - pub anchor_id: RetrievalAnchorId, - pub grain: RetrievalGrainV1, - pub hydration: HydrationStateV1, - pub encoded_bytes: u64, -} - -impl CompactContextRecordV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - self.anchor_id - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "compact context record anchor", - }) - } -} - -/// One explicit compact-context omission. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct CompactContextOmissionV1 { - pub anchor_id: Option, - pub reason: ContextOmissionReasonV1, -} - -impl CompactContextOmissionV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - if let Some(anchor_id) = &self.anchor_id { - anchor_id - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "compact context omission anchor", - })?; - } - Ok(()) - } -} - -/// One conflict retained in compact context instead of silently selecting a side. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct CompactContextConflictV1 { - pub anchor_id: RetrievalAnchorId, - pub supporting_anchor_ids: BTreeSet, -} - -impl CompactContextConflictV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - self.anchor_id - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "compact context conflict anchor", - })?; - for anchor_id in &self.supporting_anchor_ids { - anchor_id - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "compact context conflict supporting anchor", - })?; - } - Ok(()) - } -} - -/// One typed temporal edge needed to interpret compact-context evolution. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct CompactContextLineageEdgeV1 { - pub kind: TemporalAssertionKindV1, - pub subject_anchor_id: RetrievalAnchorId, - pub object_anchor_id: RetrievalAnchorId, - pub knowledge_at: UtcMicros, - pub authority: SessionAuthorityClassV1, - pub authorized: bool, - pub supporting_anchor_ids: BTreeSet, -} - -impl CompactContextLineageEdgeV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - if self.subject_anchor_id == self.object_anchor_id { - return Err(SessionContractError::AssertionSelfReference); - } - for (field, anchor_id) in [ - ("compact context lineage subject", &self.subject_anchor_id), - ("compact context lineage object", &self.object_anchor_id), - ] { - anchor_id - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { field })?; - } - for anchor_id in &self.supporting_anchor_ids { - anchor_id - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "compact context lineage supporting anchor", - })?; - } - Ok(()) - } -} - -/// Anchor-only compact-context assembly result. -#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct CompactContextBundleV1 { - pub records: Vec, - pub omissions: Vec, - pub continuation_anchors: Vec, - pub coverage: TemporalCoverageCountsV1, - pub conflicts: Vec, - pub lineage: Vec, - pub encoded_bytes: u64, -} - -impl CompactContextBundleV1 { - pub fn validate(&self) -> Result<(), SessionContractError> { - let mut anchors = BTreeSet::new(); - let mut encoded_bytes = 0_u64; - for record in &self.records { - record.validate()?; - if !anchors.insert(record.anchor_id.clone()) { - return Err(SessionContractError::DuplicateContextAnchor); - } - encoded_bytes = encoded_bytes - .checked_add(record.encoded_bytes) - .ok_or(SessionContractError::CompactContextEncodedBytesOverflow)?; - } - for anchor in &self.continuation_anchors { - anchor - .validate() - .map_err(|_| SessionContractError::InvalidIdentity { - field: "compact context continuation anchor", - })?; - if !anchors.insert(anchor.clone()) { - return Err(SessionContractError::DuplicateContextAnchor); - } - } - for omission in &self.omissions { - omission.validate()?; - if let Some(anchor_id) = &omission.anchor_id - && !anchors.insert(anchor_id.clone()) - { - return Err(SessionContractError::DuplicateContextAnchor); - } - } - for conflict in &self.conflicts { - conflict.validate()?; - } - for edge in &self.lineage { - edge.validate()?; - } - if self.encoded_bytes != encoded_bytes { - return Err(SessionContractError::CompactContextEncodedBytesMismatch); - } - Ok(()) - } -} - -impl<'de> Deserialize<'de> for CompactContextBundleV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - records: Vec, - omissions: Vec, - continuation_anchors: Vec, - #[serde(default)] - coverage: TemporalCoverageCountsV1, - #[serde(default)] - conflicts: Vec, - #[serde(default)] - lineage: Vec, - encoded_bytes: u64, - } - - let wire = Wire::deserialize(deserializer)?; - let bundle = Self { - records: wire.records, - omissions: wire.omissions, - continuation_anchors: wire.continuation_anchors, - coverage: wire.coverage, - conflicts: wire.conflicts, - lineage: wire.lineage, - encoded_bytes: wire.encoded_bytes, - }; - bundle.validate().map_err(serde::de::Error::custom)?; - Ok(bundle) - } -} - -/// Representative-view counts that complement shard-level [`crate::CoverageReportV1`]. -#[derive( - Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, -)] -#[serde(deny_unknown_fields)] -pub struct TemporalCoverageCountsV1 { - pub visible: u64, - pub hidden: u64, - pub unknown: u64, - pub redacted: u64, -} - -impl TemporalCoverageCountsV1 { - pub const fn total(self) -> Option { - match self.visible.checked_add(self.hidden) { - Some(total) => match total.checked_add(self.unknown) { - Some(total) => total.checked_add(self.redacted), - None => None, - }, - None => None, - } - } - - pub const fn has_withheld_or_unknown(self) -> bool { - self.hidden != 0 || self.unknown != 0 || self.redacted != 0 - } -} - -/// Monotonic provider or projector position for one session source. -#[derive( - Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, -)] -#[serde(transparent)] -pub struct SessionSourceFrontierV1(u64); - -impl SessionSourceFrontierV1 { - pub const fn new(value: u64) -> Self { - Self(value) - } - - pub const fn value(self) -> u64 { - self.0 - } - - pub const fn lag_from(self, target: Self) -> u64 { - target.0.saturating_sub(self.0) - } -} - -/// Closed time interval on one temporal axis. -#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(deny_unknown_fields)] -pub struct ClosedUtcIntervalV1 { - from_inclusive: Option, - through_inclusive: Option, -} - -impl ClosedUtcIntervalV1 { - pub fn new( - from_inclusive: Option, - through_inclusive: Option, - ) -> Result { - if from_inclusive.is_none() && through_inclusive.is_none() { - return Err(SessionContractError::EmptyCoverageInterval); - } - if matches!( - (from_inclusive, through_inclusive), - (Some(from), Some(through)) if from > through - ) { - return Err(SessionContractError::ReversedCoverageInterval); - } - Ok(Self { - from_inclusive, - through_inclusive, - }) - } - - pub const fn from_inclusive(self) -> Option { - self.from_inclusive - } - - pub const fn through_inclusive(self) -> Option { - self.through_inclusive - } -} - -impl<'de> Deserialize<'de> for ClosedUtcIntervalV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - from_inclusive: Option, - through_inclusive: Option, - } - - let wire = Wire::deserialize(deserializer)?; - Self::new(wire.from_inclusive, wire.through_inclusive).map_err(serde::de::Error::custom) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(tag = "kind", content = "interval", rename_all = "snake_case")] -pub enum ValidCoverageIntervalV1 { - Known(ClosedUtcIntervalV1), - Unknown, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(deny_unknown_fields)] -pub struct SessionSourceCoverageIntervalV1 { - pub knowledge: ClosedUtcIntervalV1, - pub valid: ValidCoverageIntervalV1, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(deny_unknown_fields)] -pub struct SessionTemporalCoverageRequestV1 { - mode: TemporalModeV1, -} - -impl SessionTemporalCoverageRequestV1 { - pub const fn new(mode: TemporalModeV1) -> Self { - Self { mode } - } - - pub const fn mode(&self) -> TemporalModeV1 { - self.mode - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum SessionSourceCoverageStateV1 { - Fresh, - Stale, - Partial, - Locked, - Redacted, - RetentionWithheld, - Unavailable, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum SessionSourceCoverageReasonV1 { - CaughtUp, - ProjectionBehindSource { - lag: u64, - }, - SourceBehindTarget { - lag: u64, - }, - ProjectionAndSourceBehind { - projection_lag: u64, - source_lag: u64, - }, - Locked, - Redacted, - RetentionWithheld, - Unavailable, -} - -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct SessionSourceCoverageV1 { - source_id: SessionSourceIdV1, - observed_frontier: SessionSourceFrontierV1, - committed_frontier: SessionSourceFrontierV1, - target_watermark: SessionSourceFrontierV1, - request: SessionTemporalCoverageRequestV1, - covered_intervals: Vec, - missing_intervals: Vec, - state: SessionSourceCoverageStateV1, - reason: SessionSourceCoverageReasonV1, -} - -impl SessionSourceCoverageV1 { - #[allow(clippy::too_many_arguments)] - pub fn new( - source_id: SessionSourceIdV1, - observed_frontier: SessionSourceFrontierV1, - committed_frontier: SessionSourceFrontierV1, - target_watermark: SessionSourceFrontierV1, - request: SessionTemporalCoverageRequestV1, - mut covered_intervals: Vec, - mut missing_intervals: Vec, - state: SessionSourceCoverageStateV1, - reason: SessionSourceCoverageReasonV1, - ) -> Result { - if committed_frontier > observed_frontier { - return Err(SessionContractError::InvalidSourceCoverageFrontiers); - } - covered_intervals.sort(); - missing_intervals.sort(); - if has_duplicate_intervals(&covered_intervals) - || has_duplicate_intervals(&missing_intervals) - || covered_intervals.iter().any(|covered| { - missing_intervals - .iter() - .any(|missing| coverage_intervals_touch_or_overlap(covered, missing)) - }) - || !coverage_state_matches_reason(state, &reason) - { - return Err(if coverage_state_matches_reason(state, &reason) { - SessionContractError::NonCanonicalCoverageIntervals - } else { - SessionContractError::InvalidSourceCoverageState - }); - } - Ok(Self { - source_id, - observed_frontier, - committed_frontier, - target_watermark, - request, - covered_intervals, - missing_intervals, - state, - reason, - }) - } - - pub fn from_frontiers( - source_id: SessionSourceIdV1, - observed_frontier: SessionSourceFrontierV1, - committed_frontier: SessionSourceFrontierV1, - target_watermark: SessionSourceFrontierV1, - request: SessionTemporalCoverageRequestV1, - ) -> Result { - let projection_lag = committed_frontier.lag_from(observed_frontier); - let source_lag = observed_frontier.lag_from(target_watermark); - let (state, reason) = match (projection_lag, source_lag) { - (0, 0) => ( - SessionSourceCoverageStateV1::Fresh, - SessionSourceCoverageReasonV1::CaughtUp, - ), - (0, lag) => ( - SessionSourceCoverageStateV1::Partial, - SessionSourceCoverageReasonV1::SourceBehindTarget { lag }, - ), - (lag, 0) => ( - SessionSourceCoverageStateV1::Stale, - SessionSourceCoverageReasonV1::ProjectionBehindSource { lag }, - ), - (projection_lag, source_lag) => ( - SessionSourceCoverageStateV1::Partial, - SessionSourceCoverageReasonV1::ProjectionAndSourceBehind { - projection_lag, - source_lag, - }, - ), - }; - Self::new( - source_id, - observed_frontier, - committed_frontier, - target_watermark, - request, - Vec::new(), - Vec::new(), - state, - reason, - ) - } - - pub fn source_id(&self) -> &SessionSourceIdV1 { - &self.source_id - } - - pub const fn observed_frontier(&self) -> SessionSourceFrontierV1 { - self.observed_frontier - } - - pub const fn committed_frontier(&self) -> SessionSourceFrontierV1 { - self.committed_frontier - } - - pub const fn target_watermark(&self) -> SessionSourceFrontierV1 { - self.target_watermark - } - - pub fn request(&self) -> &SessionTemporalCoverageRequestV1 { - &self.request - } - - pub fn covered_intervals(&self) -> &[SessionSourceCoverageIntervalV1] { - &self.covered_intervals - } - - pub fn missing_intervals(&self) -> &[SessionSourceCoverageIntervalV1] { - &self.missing_intervals - } - - pub const fn state(&self) -> SessionSourceCoverageStateV1 { - self.state - } - - pub fn reason(&self) -> &SessionSourceCoverageReasonV1 { - &self.reason - } - - pub const fn frontier_lag(&self) -> u64 { - self.target_watermark - .0 - .saturating_sub(self.committed_frontier.0) - } -} - -impl<'de> Deserialize<'de> for SessionSourceCoverageV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - source_id: SessionSourceIdV1, - observed_frontier: SessionSourceFrontierV1, - committed_frontier: SessionSourceFrontierV1, - target_watermark: SessionSourceFrontierV1, - request: SessionTemporalCoverageRequestV1, - covered_intervals: Vec, - missing_intervals: Vec, - state: SessionSourceCoverageStateV1, - reason: SessionSourceCoverageReasonV1, - } - - let wire = Wire::deserialize(deserializer)?; - Self::new( - wire.source_id, - wire.observed_frontier, - wire.committed_frontier, - wire.target_watermark, - wire.request, - wire.covered_intervals, - wire.missing_intervals, - wire.state, - wire.reason, - ) - .map_err(serde::de::Error::custom) - } -} - -fn has_duplicate_intervals(intervals: &[SessionSourceCoverageIntervalV1]) -> bool { - intervals.iter().enumerate().any(|(index, left)| { - intervals[index + 1..] - .iter() - .any(|right| coverage_intervals_touch_or_overlap(left, right)) - }) -} - -fn coverage_intervals_touch_or_overlap( - left: &SessionSourceCoverageIntervalV1, - right: &SessionSourceCoverageIntervalV1, -) -> bool { - intervals_touch_or_overlap(left.knowledge, right.knowledge) - && valid_intervals_touch_or_overlap(&left.valid, &right.valid) -} - -fn valid_intervals_touch_or_overlap( - left: &ValidCoverageIntervalV1, - right: &ValidCoverageIntervalV1, -) -> bool { - match (left, right) { - (ValidCoverageIntervalV1::Unknown, ValidCoverageIntervalV1::Unknown) => true, - (ValidCoverageIntervalV1::Known(left), ValidCoverageIntervalV1::Known(right)) => { - intervals_touch_or_overlap(*left, *right) - } - _ => false, - } -} - -fn intervals_touch_or_overlap(left: ClosedUtcIntervalV1, right: ClosedUtcIntervalV1) -> bool { - let left_from = left.from_inclusive.map_or(i64::MIN, |value| value.0); - let left_through = left.through_inclusive.map_or(i64::MAX, |value| value.0); - let right_from = right.from_inclusive.map_or(i64::MIN, |value| value.0); - let right_through = right.through_inclusive.map_or(i64::MAX, |value| value.0); - left_from <= right_through.saturating_add(1) && right_from <= left_through.saturating_add(1) -} - -fn coverage_state_matches_reason( - state: SessionSourceCoverageStateV1, - reason: &SessionSourceCoverageReasonV1, -) -> bool { - matches!( - (state, reason), - ( - SessionSourceCoverageStateV1::Fresh, - SessionSourceCoverageReasonV1::CaughtUp - ) | ( - SessionSourceCoverageStateV1::Stale, - SessionSourceCoverageReasonV1::ProjectionBehindSource { .. } - ) | ( - SessionSourceCoverageStateV1::Partial, - SessionSourceCoverageReasonV1::SourceBehindTarget { .. } - | SessionSourceCoverageReasonV1::ProjectionAndSourceBehind { .. } - ) | ( - SessionSourceCoverageStateV1::Locked, - SessionSourceCoverageReasonV1::Locked - ) | ( - SessionSourceCoverageStateV1::Redacted, - SessionSourceCoverageReasonV1::Redacted - ) | ( - SessionSourceCoverageStateV1::RetentionWithheld, - SessionSourceCoverageReasonV1::RetentionWithheld - ) | ( - SessionSourceCoverageStateV1::Unavailable, - SessionSourceCoverageReasonV1::Unavailable - ) - ) -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(rename_all = "snake_case")] -pub enum SessionSourceCoverageAggregateStateV1 { - Fresh, - Stale, - Partial, -} - -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct SessionSourceCoverageReceiptV1 { - request: SessionTemporalCoverageRequestV1, - sources: Vec, - aggregate_state: SessionSourceCoverageAggregateStateV1, -} - -impl SessionSourceCoverageReceiptV1 { - pub fn new( - request: SessionTemporalCoverageRequestV1, - mut sources: Vec, - ) -> Result { - if sources.is_empty() { - return Err(SessionContractError::SourceCoverageRequired); - } - sources.sort_by(|left, right| left.source_id.cmp(&right.source_id)); - if sources - .windows(2) - .any(|pair| pair[0].source_id == pair[1].source_id) - { - return Err(SessionContractError::DuplicateSourceCoverage); - } - if sources.iter().any(|source| source.request != request) { - return Err(SessionContractError::SourceCoverageRequestMismatch); - } - let all_fresh = sources - .iter() - .all(|source| source.state == SessionSourceCoverageStateV1::Fresh); - let all_stale = sources - .iter() - .all(|source| source.state == SessionSourceCoverageStateV1::Stale); - let aggregate_state = if all_fresh { - SessionSourceCoverageAggregateStateV1::Fresh - } else if all_stale { - SessionSourceCoverageAggregateStateV1::Stale - } else { - SessionSourceCoverageAggregateStateV1::Partial - }; - Ok(Self { - request, - sources, - aggregate_state, - }) - } - - pub fn request(&self) -> &SessionTemporalCoverageRequestV1 { - &self.request - } - - pub fn sources(&self) -> &[SessionSourceCoverageV1] { - &self.sources - } - - pub const fn aggregate_state(&self) -> SessionSourceCoverageAggregateStateV1 { - self.aggregate_state - } - - pub fn max_frontier_lag(&self) -> u64 { - self.sources - .iter() - .map(SessionSourceCoverageV1::frontier_lag) - .max() - .unwrap_or(0) - } -} - -impl<'de> Deserialize<'de> for SessionSourceCoverageReceiptV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - request: SessionTemporalCoverageRequestV1, - sources: Vec, - aggregate_state: SessionSourceCoverageAggregateStateV1, - } - - let wire = Wire::deserialize(deserializer)?; - let receipt = Self::new(wire.request, wire.sources).map_err(serde::de::Error::custom)?; - if receipt.aggregate_state != wire.aggregate_state { - return Err(serde::de::Error::custom( - SessionContractError::InvalidSourceCoverageState, - )); - } - Ok(receipt) - } -} - -#[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[serde(deny_unknown_fields)] -pub struct SessionRefreshSourceTargetV1 { - source_id: SessionSourceIdV1, - observed_frontier: SessionSourceFrontierV1, - target_watermark: SessionSourceFrontierV1, -} - -impl SessionRefreshSourceTargetV1 { - pub fn new( - source_id: SessionSourceIdV1, - observed_frontier: SessionSourceFrontierV1, - target_watermark: SessionSourceFrontierV1, - ) -> Result { - if target_watermark < observed_frontier { - return Err(SessionContractError::InvalidRefreshSourceFrontier); - } - Ok(Self { - source_id, - observed_frontier, - target_watermark, - }) - } - - pub fn source_id(&self) -> &SessionSourceIdV1 { - &self.source_id - } - - pub const fn observed_frontier(&self) -> SessionSourceFrontierV1 { - self.observed_frontier - } - - pub const fn target_watermark(&self) -> SessionSourceFrontierV1 { - self.target_watermark - } -} - -impl<'de> Deserialize<'de> for SessionRefreshSourceTargetV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - source_id: SessionSourceIdV1, - observed_frontier: SessionSourceFrontierV1, - target_watermark: SessionSourceFrontierV1, - } - - let wire = Wire::deserialize(deserializer)?; - Self::new( - wire.source_id, - wire.observed_frontier, - wire.target_watermark, - ) - .map_err(serde::de::Error::custom) - } -} - -#[derive(Clone, Debug, Serialize, PartialEq, Eq, Hash)] -#[serde(deny_unknown_fields)] -pub struct SessionRefreshKeyV1 { - store_root_id: String, - session_id: SessionId, - sources: Vec, - projector_version: String, - configuration_digest: String, -} - -impl SessionRefreshKeyV1 { - pub fn new( - store_root_id: impl Into, - session_id: SessionId, - mut sources: Vec, - projector_version: impl Into, - configuration_digest: impl Into, - ) -> Result { - let store_root_id = canonical_component(store_root_id.into(), "store_root_id")?; - let projector_version = canonical_component(projector_version.into(), "projector_version")?; - let configuration_digest = - canonical_component(configuration_digest.into(), "configuration_digest")?; - if sources.is_empty() { - return Err(SessionContractError::RefreshSourcesRequired); - } - sources.sort(); - if sources - .windows(2) - .any(|pair| pair[0].source_id == pair[1].source_id) - { - return Err(SessionContractError::DuplicateRefreshSource); - } - Ok(Self { - store_root_id, - session_id, - sources, - projector_version, - configuration_digest, - }) - } - - pub fn sources(&self) -> &[SessionRefreshSourceTargetV1] { - &self.sources - } - - pub fn store_root_id(&self) -> &str { - &self.store_root_id - } - - pub fn session_id(&self) -> &SessionId { - &self.session_id - } - - pub fn projector_version(&self) -> &str { - &self.projector_version - } - - pub fn configuration_digest(&self) -> &str { - &self.configuration_digest - } -} - -impl<'de> Deserialize<'de> for SessionRefreshKeyV1 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct Wire { - store_root_id: String, - session_id: SessionId, - sources: Vec, - projector_version: String, - configuration_digest: String, - } - - let wire = Wire::deserialize(deserializer)?; - Self::new( - wire.store_root_id, - wire.session_id, - wire.sources, - wire.projector_version, - wire.configuration_digest, - ) - .map_err(serde::de::Error::custom) - } -} - -fn canonical_component(value: String, field: &'static str) -> Result { - if value.is_empty() - || value.trim() != value - || value.len() > 512 - || value.chars().any(char::is_control) - { - return Err(SessionContractError::InvalidIdentity { field }); - } - Ok(value) -} - -#[cfg(test)] -mod source_freshness_tests { - use super::*; - - fn source(value: &str) -> SessionSourceIdV1 { - SessionSourceIdV1::new(value).unwrap() - } - - #[test] - fn validity_intervals_reject_empty_and_reversed_bounds() { - assert_eq!( - ClosedUtcIntervalV1::new(None, None), - Err(SessionContractError::EmptyCoverageInterval) - ); - assert_eq!( - ClosedUtcIntervalV1::new(Some(UtcMicros(20)), Some(UtcMicros(10))), - Err(SessionContractError::ReversedCoverageInterval) - ); - - let request = SessionTemporalCoverageRequestV1::new(TemporalModeV1::Current); - let interval = |from, through| SessionSourceCoverageIntervalV1 { - knowledge: ClosedUtcIntervalV1::new(Some(UtcMicros(from)), Some(UtcMicros(through))) - .unwrap(), - valid: ValidCoverageIntervalV1::Unknown, - }; - assert_eq!( - SessionSourceCoverageV1::new( - source("cursor"), - SessionSourceFrontierV1::new(10), - SessionSourceFrontierV1::new(10), - SessionSourceFrontierV1::new(10), - request, - vec![interval(1, 5), interval(6, 10)], - Vec::new(), - SessionSourceCoverageStateV1::Fresh, - SessionSourceCoverageReasonV1::CaughtUp, - ), - Err(SessionContractError::NonCanonicalCoverageIntervals) - ); - } - - #[test] - fn source_freshness_is_derived_from_observed_projected_and_target_frontiers() { - let request = SessionTemporalCoverageRequestV1::new(TemporalModeV1::Current); - let stale = SessionSourceCoverageV1::from_frontiers( - source("cursor"), - SessionSourceFrontierV1::new(10), - SessionSourceFrontierV1::new(8), - SessionSourceFrontierV1::new(10), - request.clone(), - ) - .unwrap(); - assert_eq!(stale.state(), SessionSourceCoverageStateV1::Stale); - assert_eq!( - stale.reason(), - &SessionSourceCoverageReasonV1::ProjectionBehindSource { lag: 2 } - ); - - let partial = SessionSourceCoverageV1::from_frontiers( - source("claude"), - SessionSourceFrontierV1::new(8), - SessionSourceFrontierV1::new(8), - SessionSourceFrontierV1::new(10), - request, - ) - .unwrap(); - assert_eq!(partial.state(), SessionSourceCoverageStateV1::Partial); - assert_eq!( - partial.reason(), - &SessionSourceCoverageReasonV1::SourceBehindTarget { lag: 2 } - ); - } - - #[test] - fn aggregate_receipt_preserves_sources_and_mixed_freshness() { - let request = SessionTemporalCoverageRequestV1::new(TemporalModeV1::Current); - let receipt = SessionSourceCoverageReceiptV1::new( - request.clone(), - vec![ - SessionSourceCoverageV1::from_frontiers( - source("cursor"), - SessionSourceFrontierV1::new(10), - SessionSourceFrontierV1::new(10), - SessionSourceFrontierV1::new(10), - request.clone(), - ) - .unwrap(), - SessionSourceCoverageV1::from_frontiers( - source("claude"), - SessionSourceFrontierV1::new(10), - SessionSourceFrontierV1::new(7), - SessionSourceFrontierV1::new(10), - request, - ) - .unwrap(), - ], - ) - .unwrap(); - - assert_eq!(receipt.sources().len(), 2); - assert_eq!( - receipt.aggregate_state(), - SessionSourceCoverageAggregateStateV1::Partial - ); - assert_eq!(receipt.max_frontier_lag(), 3); - } - - #[test] - fn refresh_key_canonicalizes_sources_and_round_trips() { - let target = |name: &str| { - SessionRefreshSourceTargetV1::new( - source(name), - SessionSourceFrontierV1::new(8), - SessionSourceFrontierV1::new(10), - ) - .unwrap() - }; - let key = SessionRefreshKeyV1::new( - "root.1", - SessionId::new("session.1").unwrap(), - vec![target("cursor"), target("claude")], - "projector.v1", - "sha256:configuration", - ) - .unwrap(); - assert_eq!(key.sources()[0].source_id().as_str(), "claude"); - let encoded = serde_json::to_string(&key).unwrap(); - assert_eq!( - serde_json::from_str::(&encoded).unwrap(), - key - ); - } -} diff --git a/crates/tracedecay-query/src/search_quality/packaged.rs b/crates/tracedecay-query/src/search_quality/packaged.rs index 2af1f78322..2584fcd2eb 100644 --- a/crates/tracedecay-query/src/search_quality/packaged.rs +++ b/crates/tracedecay-query/src/search_quality/packaged.rs @@ -19,93 +19,87 @@ const FILES: &[(&str, &[u8])] = &[ ( WORKLOAD_PATH, include_bytes!( - "../../assets/runtime-root/tests/fixtures/search_quality/query-lexical-graph-workload-v1.json" + "../../../../tests/fixtures/search_quality/query-lexical-graph-workload-v1.json" ), ), ( "tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/time.rs", include_bytes!( - "../../assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/time.rs" + "../../../../tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/time.rs" ), ), ( "tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/watermark.rs", include_bytes!( - "../../assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/watermark.rs" + "../../../../tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/watermark.rs" ), ), ( "tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/canonical.rs", include_bytes!( - "../../assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/canonical.rs" + "../../../../tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/canonical.rs" ), ), ( "tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/error.rs", include_bytes!( - "../../assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/error.rs" + "../../../../tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/error.rs" ), ), ( "tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/coverage.rs", include_bytes!( - "../../assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/coverage.rs" + "../../../../tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/coverage.rs" ), ), ( "tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/repository.rs", include_bytes!( - "../../assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/repository.rs" + "../../../../tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/repository.rs" ), ), ( "tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/integration.rs", include_bytes!( - "../../assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/integration.rs" + "../../../../tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/integration.rs" ), ), ( "tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/session.rs", include_bytes!( - "../../assets/runtime-root/tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/session.rs" + "../../../../tests/fixtures/search_quality/corpus/crates/tracedecay-domain/src/session.rs" ), ), ( "tests/fixtures/context_eval_project/src/auth/login.rs", - include_bytes!( - "../../assets/runtime-root/tests/fixtures/context_eval_project/src/auth/login.rs" - ), + include_bytes!("../../../../tests/fixtures/context_eval_project/src/auth/login.rs"), ), ( "tests/fixtures/context_eval_project/src/auth/session.rs", - include_bytes!( - "../../assets/runtime-root/tests/fixtures/context_eval_project/src/auth/session.rs" - ), + include_bytes!("../../../../tests/fixtures/context_eval_project/src/auth/session.rs"), ), ( "tests/fixtures/context_eval_project/src/storage/config_store.rs", include_bytes!( - "../../assets/runtime-root/tests/fixtures/context_eval_project/src/storage/config_store.rs" + "../../../../tests/fixtures/context_eval_project/src/storage/config_store.rs" ), ), ( "tests/fixtures/context_eval_project/src/net/retry.rs", - include_bytes!( - "../../assets/runtime-root/tests/fixtures/context_eval_project/src/net/retry.rs" - ), + include_bytes!("../../../../tests/fixtures/context_eval_project/src/net/retry.rs"), ), ( "tests/fixtures/sample.dockerfile", - include_bytes!("../../assets/runtime-root/tests/fixtures/sample.dockerfile"), + include_bytes!("../../../../tests/fixtures/sample.dockerfile"), ), ( "evals/agent_adoption/fixture/Cargo.lock", - include_bytes!("../../assets/runtime-root/evals/agent_adoption/fixture/Cargo.lock"), + include_bytes!("../../../../evals/agent_adoption/fixture/Cargo.lock"), ), ( "tests/fixtures/search_quality/corpus/cargo-slot/src/main.rust.fixture", include_bytes!( - "../../assets/runtime-root/tests/fixtures/search_quality/corpus/cargo-slot/src/main.rust.fixture" + "../../../../tests/fixtures/search_quality/corpus/cargo-slot/src/main.rust.fixture" ), ), ]; diff --git a/crates/tracedecay-search-eval/src/packaged_assets.rs b/crates/tracedecay-search-eval/src/packaged_assets.rs index 90083d8974..f1bd8e69cd 100644 --- a/crates/tracedecay-search-eval/src/packaged_assets.rs +++ b/crates/tracedecay-search-eval/src/packaged_assets.rs @@ -166,6 +166,12 @@ pub(crate) fn admitted_scope(_root: &Path) -> Option { #[cfg(test)] mod tests { + use std::collections::BTreeMap; + + use tracedecay_query::search_quality::{ + DirectEvaluationStatusV1, QUERY_BASELINE_PROFILE, packaged, + }; + /// The packaged evaluator carries its own workload, corpus, and Git /// authority, so `compare` needs nothing from the checkout it runs in. #[test] @@ -187,4 +193,92 @@ mod tests { ] ); } + + #[test] + fn source_and_packaged_evaluations_preserve_corpus_and_fallback_receipts() { + let assets = super::materialize().expect("materialized evaluator fixture"); + assert!(crate::root_admitted_corpus_scope(assets.root()).is_none()); + assert!( + tracedecay_runtime_core::storage::write_repository_identity_marker( + assets.root(), + "project.search-eval-source-fixture", + ) + .expect("enroll the temporary evaluator repository") + ); + let scope = crate::root_admitted_corpus_scope(assets.root()) + .expect("source evaluation admits the fixture's authoritative identity"); + assert_eq!( + scope.project_id.as_str(), + "project.search-eval-source-fixture" + ); + + let profiles = vec![QUERY_BASELINE_PROFILE.to_owned()]; + let source = crate::compare_direct( + assets.root(), + Some(&assets.workload_path()), + Some(&profiles), + crate::root_admitted_corpus_scope, + ) + .expect("source-backed evaluator execution"); + let packaged = + crate::compare_default_direct(Some(&profiles)).expect("packaged evaluator execution"); + + assert_eq!(source.workload_digest, packaged::WORKLOAD_SHA256); + assert_eq!(source.workload_digest, packaged.workload_digest); + assert_eq!(source.corpus_digest, packaged.corpus_digest); + assert_eq!(source.execution_contract, packaged.execution_contract); + assert_eq!( + source.profile_material_digests, + packaged.profile_material_digests + ); + + let workload = assets.workload(); + let expected_receipts = workload + .expected_query_fallback_digests + .iter() + .collect::>(); + for report in [&source, &packaged] { + assert_eq!(report.command, "compare"); + assert_eq!(report.status, DirectEvaluationStatusV1::Fail); + assert_eq!( + report.fixture_source_repository_commit, + workload.source_repository_commit + ); + assert_eq!( + report.fixture_source_repository_tree, + workload.source_repository_tree + ); + assert!( + report + .profiles + .iter() + .any(|profile| profile.failed_queries > 0) + ); + assert_eq!(report.profiles.len(), expected_receipts.len()); + for profile in &report.profiles { + assert_eq!(profile.profile_id, QUERY_BASELINE_PROFILE); + assert!(profile.fallback_stable); + assert!(profile.fallback_matches_expected); + } + + let observed_receipts = report + .raw_outputs + .iter() + .map(|output| { + assert_eq!(output.profile_id, QUERY_BASELINE_PROFILE); + assert_eq!(output.workload_digest, report.workload_digest); + assert_eq!(output.corpus_digest, report.corpus_digest); + assert!(output.query_fallback_matches_expected); + assert_eq!(output.fallback_digest, output.query_fallback_digest); + assert_eq!( + output.query_fallback_digest, + output.expected_query_fallback_digest + ); + (&output.partition, &output.query_fallback_digest) + }) + .collect::>(); + assert_eq!(observed_receipts.len(), report.raw_outputs.len()); + assert_eq!(observed_receipts, expected_receipts); + } + } } diff --git a/crates/tracedecay-search-eval/src/report_tests.rs b/crates/tracedecay-search-eval/src/report_tests.rs index 3814a65551..dff9d1af9b 100644 --- a/crates/tracedecay-search-eval/src/report_tests.rs +++ b/crates/tracedecay-search-eval/src/report_tests.rs @@ -49,7 +49,7 @@ fn assert_baseline_report_retains_raw_fallback_current_and_exact_ten_x_samples( profile.fallback_matches_expected, "{}:{} ranking receipt drifted from \ `expected_query_fallback_digests.{}` in \ - crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/query-lexical-graph-workload-v1.json \ + tests/fixtures/search_quality/query-lexical-graph-workload-v1.json \ ({observed}). The receipt binds ordered ranking rows and lane coverage, \ not generation or extractor-revision identity. Re-pin only that receipt \ when the ranking itself changed; do not touch the workload identity pin.", diff --git a/evals/agent_adoption/BUILD.bazel b/evals/agent_adoption/BUILD.bazel index f9f95ae7e9..c2c307da98 100644 --- a/evals/agent_adoption/BUILD.bazel +++ b/evals/agent_adoption/BUILD.bazel @@ -18,3 +18,11 @@ py_test( legacy_create_init = 0, main = "selftest.py", ) + +# BEGIN generated by scripts/bazel/gen_builds.py +filegroup( + name = "fixture", + srcs = glob(["fixture/**"]), + visibility = ["//visibility:public"], +) +# END generated by scripts/bazel/gen_builds.py diff --git a/scripts/bazel/gen_builds.py b/scripts/bazel/gen_builds.py index 366be494d0..c68a96cda9 100755 --- a/scripts/bazel/gen_builds.py +++ b/scripts/bazel/gen_builds.py @@ -211,6 +211,20 @@ NO_DEFAULT_FEATURES = "!no-default-features" +def owner_pkg(repo_rel, repo=REPO): + """Honor existing Bazel package boundaries before creating a shared package.""" + for directory in (repo_rel.parent, *repo_rel.parent.parents): + if directory == Path("."): + break + if any((repo / directory / name).is_file() for name in ("BUILD.bazel", "BUILD")): + group = repo_rel.parent.relative_to(directory) + return directory.as_posix(), "" if group == Path(".") else group.as_posix() + parts = repo_rel.parts + if parts[0] == "crates": + return "/".join(parts[:2]), "/".join(parts[2:-1]) + return parts[0], "/".join(parts[1:-1]) + + def normalize_default_alias(feats, feature_table): """Add `default` when it is only an alias for features already enabled. @@ -758,14 +772,6 @@ def srcs_expr(fragments): # shared between member crates) need a filegroup in their own package. out_of_pkg = {} # (top_dir, group_dir) -> set of crate_root paths in package - def owner_pkg(repo_rel): - """Package dir that owns repo_rel plus the group dir inside it. Crates - members own their subtree. Other top-level dirs own theirs.""" - parts = repo_rel.parts - if parts[0] == "crates": - return "/".join(parts[:2]), "/".join(parts[2:-1]) - return parts[0], "/".join(parts[1:-1]) - def group_label(repo_rel, *, sources=False): """filegroup label + pkg-relative file label for an out-of-package file, registering the group for emission in the owning package.""" @@ -1411,7 +1417,7 @@ def package_rules(pkg_dir, exports): name member crate roots, so they need none.""" rules = [] for gname, entry in sorted(by_pkg.get(pkg_dir, {}).items()): - pattern = entry.get("pattern", f'{entry["glob"]}/**') + pattern = entry.get("pattern", f'{entry["glob"]}/**' if entry["glob"] else "**") exclude = "" if entry.get("sources"): pattern += "/*.rs" @@ -1531,6 +1537,8 @@ def render_shared(path, pkg_dir): # other members reach into this one's subtree for. outputs[path] += "\n" + "\n\n".join(package_rules(pkg_dir, False)) + "\n" continue + if not path.exists() and path.with_name("BUILD").is_file(): + path = path.with_name("BUILD") rendered = render_shared(path, pkg_dir) if rendered is None: obsolete.append(path) diff --git a/scripts/test-gen-builds-default-alias.py b/scripts/test-gen-builds-default-alias.py index 5dadadb25b..6154dc268e 100755 --- a/scripts/test-gen-builds-default-alias.py +++ b/scripts/test-gen-builds-default-alias.py @@ -101,5 +101,40 @@ def test_suite_owns_its_inputs_and_explicit_shared_modules(self) -> None: self.assertEqual(sources, {root / "tests/owned/main.rs"}) +class SharedPackageOwnershipTests(unittest.TestCase): + def test_nested_package_owns_embedded_fixture(self) -> None: + gen = load_gen_builds() + with tempfile.TemporaryDirectory() as scratch: + root = Path(scratch) + package = root / "evals/agent_adoption" + package.mkdir(parents=True) + (root / "evals/BUILD.bazel").write_text("# generated parent") + for build_name in ("BUILD.bazel", "BUILD"): + build = package / build_name + build.write_text("# handwritten package") + self.assertEqual( + gen.owner_pkg(Path("evals/agent_adoption/fixture/Cargo.lock"), root), + ("evals/agent_adoption", "fixture"), + ) + self.assertEqual( + gen.owner_pkg(Path("evals/agent_adoption/input.json"), root), + ("evals/agent_adoption", ""), + ) + build.unlink() + + def test_unowned_fixtures_keep_shared_package_layout(self) -> None: + gen = load_gen_builds() + with tempfile.TemporaryDirectory() as scratch: + root = Path(scratch) + self.assertEqual( + gen.owner_pkg(Path("tests/fixtures/example.json"), root), + ("tests", "fixtures"), + ) + self.assertEqual( + gen.owner_pkg(Path("crates/example/tests/input.json"), root), + ("crates/example", "tests"), + ) + + if __name__ == "__main__": unittest.main() diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index ddda3cb21f..ead60ab383 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -22,6 +22,36 @@ exports_files(["fixtures/claude-schemas/hooks.schema.json", "fixtures/claude-sch visibility = ["//visibility:public"], ) +filegroup( + name = "fixtures_context_eval_project_src_auth", + srcs = glob(["fixtures/context_eval_project/src/auth/**"]), + visibility = ["//visibility:public"], +) + +exports_files(["fixtures/context_eval_project/src/auth/login.rs", "fixtures/context_eval_project/src/auth/session.rs"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "fixtures_context_eval_project_src_net", + srcs = glob(["fixtures/context_eval_project/src/net/**"]), + visibility = ["//visibility:public"], +) + +exports_files(["fixtures/context_eval_project/src/net/retry.rs"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "fixtures_context_eval_project_src_storage", + srcs = glob(["fixtures/context_eval_project/src/storage/**"]), + visibility = ["//visibility:public"], +) + +exports_files(["fixtures/context_eval_project/src/storage/config_store.rs"], + visibility = ["//visibility:public"], +) + filegroup( name = "fixtures_cursor-schemas", srcs = glob(["fixtures/cursor-schemas/**"]), @@ -222,6 +252,46 @@ exports_files(["fixtures/remote_tls/alternate-root.crt.pem", "fixtures/remote_tl visibility = ["//visibility:public"], ) +filegroup( + name = "fixtures_search_quality", + srcs = glob(["fixtures/search_quality/**"]), + visibility = ["//visibility:public"], +) + +exports_files(["fixtures/search_quality/query-lexical-graph-workload-v1.json"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "fixtures_search_quality_corpus_cargo-slot_src", + srcs = glob(["fixtures/search_quality/corpus/cargo-slot/src/**"]), + visibility = ["//visibility:public"], +) + +exports_files(["fixtures/search_quality/corpus/cargo-slot/src/main.rust.fixture"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "fixtures_search_quality_corpus_crates_tracedecay-domain_src", + srcs = glob(["fixtures/search_quality/corpus/crates/tracedecay-domain/src/**"]), + visibility = ["//visibility:public"], +) + +exports_files(["fixtures/search_quality/corpus/crates/tracedecay-domain/src/integration.rs", "fixtures/search_quality/corpus/crates/tracedecay-domain/src/repository.rs", "fixtures/search_quality/corpus/crates/tracedecay-domain/src/session.rs"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "fixtures_search_quality_corpus_crates_tracedecay-domain_src_research", + srcs = glob(["fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/**"]), + visibility = ["//visibility:public"], +) + +exports_files(["fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/canonical.rs", "fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/coverage.rs", "fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/error.rs", "fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/time.rs", "fixtures/search_quality/corpus/crates/tracedecay-domain/src/research/watermark.rs"], + visibility = ["//visibility:public"], +) + filegroup( name = "fixtures_transcript_golden_cline_like", srcs = glob(["fixtures/transcript_golden/cline_like/**"]), @@ -268,7 +338,7 @@ filegroup( visibility = ["//visibility:public"], ) -exports_files(["fixtures/context_eval_labeled.json", "fixtures/message_search_eval_labeled.json"], +exports_files(["fixtures/context_eval_labeled.json", "fixtures/message_search_eval_labeled.json", "fixtures/sample.dockerfile"], visibility = ["//visibility:public"], ) diff --git a/crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/query-lexical-graph-workload-v1.json b/tests/fixtures/search_quality/query-lexical-graph-workload-v1.json similarity index 100% rename from crates/tracedecay-query/assets/runtime-root/tests/fixtures/search_quality/query-lexical-graph-workload-v1.json rename to tests/fixtures/search_quality/query-lexical-graph-workload-v1.json