diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 2fa543ae2b..1766feade5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,7 +33,7 @@ body: attributes: label: Installation method options: - - cargo install + - Built from source (Bazel) - Homebrew (brew) - Scoop - Built from source diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77c8ed4556..634b67b6e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -205,18 +205,11 @@ jobs: run: | python3 scripts/test-check-packaged-lsp-bridge.py python3 scripts/test-build-mcpb.py - python3 scripts/test-resolve-installed-binary.py - python3 scripts/test-check-packaged-mcp-stdio.py - python3 scripts/test-check-distribution-feature-wiring.py - python3 scripts/test-resolve-release-source-profile.py python3 scripts/test-package-release-archive.py python3 scripts/test-plan-release-recovery.py python3 scripts/test-check-release-artifacts.py python3 scripts/test-update-release-checksums.py python3 scripts/test-neutralize-release-pr-closing-keywords.py - bash scripts/test-check-distribution-clean-source.sh - bash scripts/test-check-distribution-snapshot.sh - bash scripts/test-check-distribution-reuse.sh bash tests/macos_stable_codesign_test.sh # Exercise the release guard scripts when admitting proposed changes. @@ -702,6 +695,16 @@ jobs: archive: tar.gz epoch: ${{ steps.packaging.outputs.epoch }} + - name: Exercise shipped MCP and LSP transports + shell: bash + run: | + set -euo pipefail + binary="$GITHUB_WORKSPACE/target/x86_64-unknown-linux-gnu/release/tracedecay" + smoke_work="$(mktemp -d)" + trap 'rm -rf -- "$smoke_work"' EXIT + TRACEDECAY_BIN="$binary" bazel run //scripts:mcp-conformance-smoke.sh + bazel run //scripts:packaged_lsp_bridge -- "$binary" "$smoke_work/lsp" + # Frontend checks require no Rust compilation and must also validate drafts. # Do not serialize them behind repository-wide Rust formatting and gates. dashboard: diff --git a/.github/workflows/distribution-acceptance.yml b/.github/workflows/distribution-acceptance.yml deleted file mode 100644 index cf92401c5e..0000000000 --- a/.github/workflows/distribution-acceptance.yml +++ /dev/null @@ -1,105 +0,0 @@ -# Packaged-crate distribution battery. Visibility only; not a ship gate. -# -# GitHub release assets are the just-built production binary, its archive, -# and the MCPB that wraps that same binary. `cargo package`, extracted-crate -# rebuilds, nextest, and MCP inspector dogfood prove the unpublished crate -# graph, not those assets. Beta.40 run 35271070760 spent ~24m of the aarch64 -# ship job on that battery and then failed before MCPB. This workflow runs -# the battery on one free ubuntu-latest runner when someone dispatches it. -# -name: Distribution acceptance - -# On demand only. Nothing in this repository runs on a timer: a scheduled -# run spends a free runner slot whether or not anyone will read the result. -on: - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: distribution-acceptance-${{ github.ref }} - cancel-in-progress: true - -jobs: - distribution-acceptance: - name: Distribution acceptance (x86_64-linux) - if: github.repository == 'ScriptedAlchemy/tracedecay' - runs-on: ubuntu-latest - timeout-minutes: 240 - env: - TARGET: x86_64-unknown-linux-gnu - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 - with: - node-version: "26.12.0" - - - uses: ./.github/actions/setup-pnpm - - - uses: ./.github/actions/setup-bazel - with: - cache-key: distribution-acceptance-linux - - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.15.0" - - - name: Build dashboard - working-directory: dashboard - run: pnpm run build - - - name: Export dashboard bundle digest for skip-mode builds - shell: bash - run: | - set -euo pipefail - digest="$(python3 scripts/check-dashboard-bundle.py dashboard/app-dist --print-digest)" - echo "TRACEDECAY_SKIP_DASHBOARD_BUILD=1" >> "$GITHUB_ENV" - echo "TRACEDECAY_DASHBOARD_BUNDLE_SHA256=${digest}" >> "$GITHUB_ENV" - - # rust-toolchain.toml is the only compiler this job may install. See - # release-beta.yml: floating stable fragments rust-cache's env key. - - name: Resolve pinned toolchain - id: pin - shell: bash - run: | - python3 -c 'import pathlib, tomllib; print("toolchain=" + tomllib.loads(pathlib.Path("rust-toolchain.toml").read_text(encoding="utf-8"))["toolchain"]["channel"])' >> "$GITHUB_OUTPUT" - - - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable - with: - toolchain: ${{ steps.pin.outputs.toolchain }} - targets: ${{ env.TARGET }} - - # Own lineage. release-beta stores the `--target` layout. This battery - # compiles the packaged CLI into the host layout (`target/dist`), so - # that shared key restored nothing this job reads, and an exact-key hit - # would have skipped saving this job's artifacts. Dependency artifacts - # only: extracted packages live under a fresh temporary path every run. - # The workspace roots keep the vendored dependencies (see - # scripts/check-rust-cache-lineage.py). - - name: Cache Rust build - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - with: - shared-key: distribution-acceptance-${{ env.TARGET }} - workspaces: | - crates -> ../target - . -> target/rust-cache-lockfile-key - cache-workspace-crates: "false" - cache-on-failure: true - - # The acceptance script runs the packaged Rust-grammar proof and the - # focused packaged-CLI integration journeys through nextest. - - name: Install cargo-nextest - uses: taiki-e/install-action@9252097d36d2d3618ad98f5d873696a962b6897c # nextest - - - name: Run distribution acceptance - run: | - set -euo pipefail - cargo fetch --locked --target ${{ env.TARGET }} - lockfile_sha="$(python3 -c 'import hashlib; print(hashlib.sha256(open("Cargo.lock", "rb").read()).hexdigest())')" - CARGO_NET_OFFLINE=true bash scripts/check-distribution-acceptance.sh - actual_lockfile_sha="$(python3 -c 'import hashlib; print(hashlib.sha256(open("Cargo.lock", "rb").read()).hexdigest())')" - test "$actual_lockfile_sha" = "$lockfile_sha" diff --git a/.github/workflows/hawk.yml b/.github/workflows/hawk.yml index ef67951be6..d995692af7 100644 --- a/.github/workflows/hawk.yml +++ b/.github/workflows/hawk.yml @@ -6,8 +6,7 @@ # softening -D warnings. name: Hawk -# On demand only; see distribution-acceptance.yml for why nothing runs on a -# timer here. +# On demand only; an optional diagnostic should run when someone will read it. on: workflow_dispatch: diff --git a/README.md b/README.md index fb0965ed87..abe6f8b1ce 100644 --- a/README.md +++ b/README.md @@ -269,9 +269,9 @@ bazel build //... bazel test //... --test_output=errors ``` -Use Cargo when you need to validate Cargo packaging, a feature combination that -has no Bazel target, or a platform-specific workflow. The release workflows -continue to build the published Cargo artifact. +Bazel owns workspace builds, tests, checks, and code generation. Release +workflows package its production CLI as binary archives and MCP bundles; +Rust workspace crates are private and are not published to crates.io. On macOS `tracedecay` is ad-hoc signed as `dev.tracedecay.cli` with designated requirement `identifier "dev.tracedecay.cli"`. macOS TCC keys @@ -280,14 +280,9 @@ requirement is the binary's cdhash, so an Allow does not survive the next rebuild; the linker default identifier (`tracedecay-`) changes every build as well, and the daemon blocks in `open()` until the prompt is answered. A release build strips after linking, which mints that identifier -again from the deps filename. Export -`CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER="$PWD/scripts/macos-rustc-wrapper.sh"` -from the checkout root (the macOS CI and release jobs do) and the wrapper -signs the product once rustc has finished, so `cargo build --release` and -`cargo install --path crates/tracedecay-cli` keep the stable identity on -the binary cargo copies into place. The committed Cargo config cannot set -it, because Cargo has no per-target wrapper and Windows cannot run the -script. `install.sh` and `tracedecay update` +again from the deps filename. The shared release build action applies +`scripts/macos-stable-codesign.sh` to the Bazel-built binary before packaging. +`install.sh` and `tracedecay update` apply the same identity to the installed file after the archive checksum check when the binary is unsigned or ad-hoc signed, including an ad-hoc signature whose identifier already matches but whose requirement is still diff --git a/crates/tracedecay-application/Cargo.toml b/crates/tracedecay-application/Cargo.toml index e115500c39..113321fcd2 100644 --- a/crates/tracedecay-application/Cargo.toml +++ b/crates/tracedecay-application/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" repository = "https://github.com/ScriptedAlchemy/tracedecay" description = "TraceDecay product use cases shared by CLI, MCP, HTTP, hooks, and daemon adapters" -include = ["/src/**"] [features] default = [] diff --git a/crates/tracedecay-capture/Cargo.toml b/crates/tracedecay-capture/Cargo.toml index 01d512ad09..ed46ce051d 100644 --- a/crates/tracedecay-capture/Cargo.toml +++ b/crates/tracedecay-capture/Cargo.toml @@ -10,7 +10,6 @@ description = "Bounded event capture primitives for TraceDecay" readme = "README.md" keywords = ["ai", "code-intelligence", "observability", "capture"] categories = ["development-tools", "parsing"] -include = ["/src/**", "/tests/**", "/README.md"] [dependencies] chrono = { version = "0.4.45", default-features = false, features = ["std"] } diff --git a/crates/tracedecay-cli/Cargo.toml b/crates/tracedecay-cli/Cargo.toml index 1f2f7cfb21..baa85bf6c5 100644 --- a/crates/tracedecay-cli/Cargo.toml +++ b/crates/tracedecay-cli/Cargo.toml @@ -6,18 +6,6 @@ edition.workspace = true description = "TraceDecay command-line interface and daemon binary" license = "MIT" repository = "https://github.com/ScriptedAlchemy/tracedecay" -# Packaged-crate whitelist. `/dashboard/app-dist/**` is staged into the crate -# directory by release packaging (the distribution gate validates it); the -# packaged-crate mode in build.rs embeds it from there, mirroring the old -# root-crate arrangement. -include = [ - "/build.rs", - "/build-support/**", - "/src/**", - "/tests/**", - "/dashboard/app-dist/**", - "!/src/resources/logo.png", -] [[bin]] name = "tracedecay" diff --git a/crates/tracedecay-cli/build.rs b/crates/tracedecay-cli/build.rs index 028e9b36a0..7f9e3e05ac 100644 --- a/crates/tracedecay-cli/build.rs +++ b/crates/tracedecay-cli/build.rs @@ -110,22 +110,6 @@ impl EmbeddedDashboard { ) } - fn packaged( - asset_paths: Vec, - digest_hex: String, - app_dist: &Path, - out_dir: &Path, - ) -> Result> { - Self::from_sources( - &asset_paths, - "CARGO_MANIFEST_DIR", - "/dashboard/app-dist", - app_dist, - out_dir, - digest_hex, - ) - } - fn from_sources( asset_paths: &[String], identity_include_env: &'static str, @@ -233,30 +217,18 @@ impl EmbeddedDashboard { /// `TRACEDECAY_SKIP_DASHBOARD_BUILD` is set the prebuilt `dashboard/app-dist` /// is staged instead and must match the digest /// `TRACEDECAY_DASHBOARD_BUNDLE_SHA256` names, so a skip can never embed -/// unproven bytes. Packaged crates carry a staged `dashboard/app-dist` whose -/// integrity Cargo's package checksums already guarantee. A missing or invalid -/// bundle always fails the build; there is no empty-assets fallback. +/// unproven bytes. A missing or invalid bundle always fails the build; +/// there is no empty-assets fallback. fn embed_dashboard( manifest_dir: &Path, out_dir: &Path, ) -> Result> { - let package_local_dashboard = manifest_dir.join("dashboard"); - if package_local_dashboard.is_dir() { - // Packaged-crate mode: release packaging staged the bundle into the - // crate directory and Cargo's checksums are the integrity authority. - let app_dist = package_local_dashboard.join("app-dist"); - let asset_paths = dashboard_manifest::dashboard_asset_paths(&app_dist)?; - let digest_hex = dashboard_bundle::bundle_digest(&app_dist, &asset_paths)?; - return EmbeddedDashboard::packaged(asset_paths, digest_hex, &app_dist, out_dir); - } - let repository_root = manifest_dir.join(REPOSITORY_ROOT_FROM_CRATE); let dashboard = repository_root.join("dashboard"); if !dashboard.join("package.json").is_file() { return Err(format!( - "no dashboard to embed: {} has no package-local dashboard/ and {} has no \ - package.json; a tracedecay binary cannot build without its dashboard bundle", - manifest_dir.display(), + "no dashboard to embed: {} has no package.json; a tracedecay binary cannot \ + build without its dashboard bundle", dashboard.display(), ) .into()); diff --git a/crates/tracedecay-code-extraction/Cargo.toml b/crates/tracedecay-code-extraction/Cargo.toml index 13a6478f9d..b7866714f8 100644 --- a/crates/tracedecay-code-extraction/Cargo.toml +++ b/crates/tracedecay-code-extraction/Cargo.toml @@ -7,15 +7,6 @@ license = "MIT" repository = "https://github.com/ScriptedAlchemy/tracedecay" description = "Tree-sitter language extraction for TraceDecay" build = "build.rs" -include = [ - "/src/**", - "/fixtures/**", - "/tests/**", - "/vendor/tree-sitter-wgsl/src/**", - "/vendor/tree-sitter-wgsl/LICENSE", - "/vendor/tree-sitter-wgsl/PROVENANCE.md", - "/build.rs", -] [features] default = ["lite", "medium", "full"] diff --git a/crates/tracedecay-code-index/Cargo.toml b/crates/tracedecay-code-index/Cargo.toml index 3391129fee..b295f1159b 100644 --- a/crates/tracedecay-code-index/Cargo.toml +++ b/crates/tracedecay-code-index/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" repository = "https://github.com/ScriptedAlchemy/tracedecay" description = "In-process structural code search for TraceDecay" -include = ["/src/**"] [features] default = [ diff --git a/crates/tracedecay-global-db/Cargo.toml b/crates/tracedecay-global-db/Cargo.toml index 00a13a194b..8113fe17c1 100644 --- a/crates/tracedecay-global-db/Cargo.toml +++ b/crates/tracedecay-global-db/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" description = "TraceDecay user-level global database: project registry, observations, session temporal store" repository = "https://github.com/ScriptedAlchemy/tracedecay" -include = ["/src/**"] [features] # Explicit opt-in (same policy as the query kernel): exposes the registered diff --git a/crates/tracedecay-graph-db/Cargo.toml b/crates/tracedecay-graph-db/Cargo.toml index 57b03a6a9c..dc92baceb0 100644 --- a/crates/tracedecay-graph-db/Cargo.toml +++ b/crates/tracedecay-graph-db/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" description = "Embedded graph storage boundary for TraceDecay" repository = "https://github.com/ScriptedAlchemy/tracedecay" -include = ["/src/**"] [features] test-helpers = [] diff --git a/crates/tracedecay-graph-query/Cargo.toml b/crates/tracedecay-graph-query/Cargo.toml index cc7cd47204..d332ee0f05 100644 --- a/crates/tracedecay-graph-query/Cargo.toml +++ b/crates/tracedecay-graph-query/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" repository = "https://github.com/ScriptedAlchemy/tracedecay" description = "Generation-pinned verified code-graph queries and code-index-backed source readers" -include = ["/src/**"] [dependencies] serde_json = "1" diff --git a/crates/tracedecay-lcm/Cargo.toml b/crates/tracedecay-lcm/Cargo.toml index 3310bae4cb..c772e5e8f8 100644 --- a/crates/tracedecay-lcm/Cargo.toml +++ b/crates/tracedecay-lcm/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" description = "Lossless context memory engine: contracts, raw ingest, summary DAG, payload authority, retrieval, GC, and retention" repository = "https://github.com/ScriptedAlchemy/tracedecay" -include = ["/src/**", "/tests/**"] [features] # Repo-wide `test-helpers` convention: exposes this crate's test-only helper diff --git a/crates/tracedecay-privacy/Cargo.toml b/crates/tracedecay-privacy/Cargo.toml index 4f371b09bc..833f6cba4b 100644 --- a/crates/tracedecay-privacy/Cargo.toml +++ b/crates/tracedecay-privacy/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" repository = "https://github.com/ScriptedAlchemy/tracedecay" description = "TraceDecay privacy authority: credential detection, record sanitization, and protected structural identifiers" -include = ["/src/**"] [features] default = [] diff --git a/crates/tracedecay-query/Cargo.toml b/crates/tracedecay-query/Cargo.toml index 362fd8d088..2a4a29cf81 100644 --- a/crates/tracedecay-query/Cargo.toml +++ b/crates/tracedecay-query/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" repository = "https://github.com/ScriptedAlchemy/tracedecay" description = "TraceDecay exact/lexical/graph code retrieval query kernel and search-quality candidate/profile inputs" -include = ["/src/**", "/assets/**"] [dependencies] # Raw deflate for lexical row text; already in the tree through diff --git a/crates/tracedecay-search-eval/Cargo.toml b/crates/tracedecay-search-eval/Cargo.toml index 665997bc01..9e5835f145 100644 --- a/crates/tracedecay-search-eval/Cargo.toml +++ b/crates/tracedecay-search-eval/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" description = "TraceDecay search-quality evaluator: exact/lexical/graph candidate generation and direct evaluation" repository = "https://github.com/ScriptedAlchemy/tracedecay" -include = ["/assets/**", "/src/**"] [dependencies] clap = { version = "4.6", features = ["derive"] } diff --git a/crates/tracedecay-session-memory/Cargo.toml b/crates/tracedecay-session-memory/Cargo.toml index af4a96fc80..753b8ffe52 100644 --- a/crates/tracedecay-session-memory/Cargo.toml +++ b/crates/tracedecay-session-memory/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" repository = "https://github.com/ScriptedAlchemy/tracedecay" description = "Session retrieval, project memory, provider usage, and runtime telemetry use cases shared by CLI, MCP, and host-admission adapters" -include = ["/src/**"] [features] default = [] diff --git a/crates/tracedecay-session-temporal-store/Cargo.toml b/crates/tracedecay-session-temporal-store/Cargo.toml index 8339294bfb..7ff4d117c8 100644 --- a/crates/tracedecay-session-temporal-store/Cargo.toml +++ b/crates/tracedecay-session-temporal-store/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" description = "Session-temporal projection, retrieval, and LCM hydration store" repository = "https://github.com/ScriptedAlchemy/tracedecay" -include = ["/src/**"] [features] # Exposes package-owned fixture constructors to dependent package tests only. diff --git a/crates/tracedecay-sessions/Cargo.toml b/crates/tracedecay-sessions/Cargo.toml index 7174b5783e..9ac8268d1d 100644 --- a/crates/tracedecay-sessions/Cargo.toml +++ b/crates/tracedecay-sessions/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" repository = "https://github.com/ScriptedAlchemy/tracedecay" description = "TraceDecay session parsing and retrieval primitives" -include = ["/src/**", "/tests/**"] [features] # Repo-wide `test-helpers` convention: exposes this crate's test-only helper diff --git a/crates/tracedecay-temporal-query/Cargo.toml b/crates/tracedecay-temporal-query/Cargo.toml index fc077a607d..6428fa2f9e 100644 --- a/crates/tracedecay-temporal-query/Cargo.toml +++ b/crates/tracedecay-temporal-query/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license = "MIT" repository = "https://github.com/ScriptedAlchemy/tracedecay" description = "TraceDecay temporal retrieval and context assembly kernel" -include = ["/src/**"] [dependencies] hex = "0.4" diff --git a/crates/tracedecay/Cargo.toml b/crates/tracedecay/Cargo.toml index 5fd95389c5..c17294afbb 100644 --- a/crates/tracedecay/Cargo.toml +++ b/crates/tracedecay/Cargo.toml @@ -11,59 +11,6 @@ homepage = "https://github.com/ScriptedAlchemy/tracedecay" readme = "../../README.md" keywords = ["code-intelligence", "knowledge-graph", "mcp", "tree-sitter", "semantic-search"] categories = ["development-tools", "command-line-utilities"] -# Explicit source-archive whitelist retained for local distribution validation. -# Frontend sources and the prebuilt dashboard/app-dist are deliberately -# excluded: the CLI package stages and embeds the dashboard bundle and -# registers it into this library at process start. -include = [ - "/CHANGELOG.md", - "/LICENSE", - "/README.md", - "/rust-toolchain.toml", - "/.cargo/config.toml", - "/src/**", - "/benches/**", - "/examples/**", - "/tests/fixtures/analytics/**", - "/tests/fixtures/packaged_host_events/**", - "/tests/fixtures/provider_normalization/**", - "/tests/fixtures/redundancy_eval_labeled.json", - "/tests/fixtures/transcript_golden/cline_like/**", - "/benchmark_data/claude-observation/**", - "/benchmark_data/session-temporal/**", - "/benchmark_data/query-code-index/**", - "/scripts/run-session-temporal-benchmark.sh", - "/plugin/.claude-plugin/**", - "/plugin/.cursor-plugin/**", - "/plugin/.codex-plugin/**", - "/plugin/.kimi-plugin/**", - "/plugin/.lsp.json", - "/plugin/.mcp.json", - "/plugin/plugin.json", - "/plugin/mcp.json", - "/plugin/README*.md", - "/plugin/chatgpt-extension/README.md", - "/plugin/chatgpt-extension/assets/icon.svg", - "/plugin/chatgpt-extension/embedded/app.html", - "/plugin/chatgpt-extension/embedded/server.mjs", - "/plugin/agents/**", - "/plugin/commands/**", - "/plugin/hooks/**", - "/plugin/mcp-cursor.json", - "/plugin/opencode/**", - "/plugin/overlays/**", - "/plugin/rules/**", - "/plugin/skills/**", - "/plugin/cursor-native-extension/package.json", - "/plugin/cursor-native-extension/README.md", - "/plugin/cursor-native-extension/LICENSE", - "/plugin/cursor-native-extension/embedded/extension.js", - "/vendor/**", - "/dashboard/hermes-wrapper/manifest.json", - "/dashboard/hermes-wrapper/plugin_api.py", - "/dashboard/hermes-wrapper/embed_proxy.py", - "/dashboard/hermes-wrapper/src/**", -] # Measured aggregate suites exceed the native five-minute medium limit. [package.metadata.bazel.test_sizes] diff --git a/docs/RELEASE-AUTOMATION.md b/docs/RELEASE-AUTOMATION.md index 62d7ea697b..1bae57a219 100644 --- a/docs/RELEASE-AUTOMATION.md +++ b/docs/RELEASE-AUTOMATION.md @@ -97,16 +97,15 @@ assets. It builds the production binary, smokes `--version`/`--help`, and packages the MCPB and archive from that same file. A source-tree inventory or a release-PR path policy is not acceptance. -`scripts/check-distribution-acceptance.sh` proves the unpublished crate -graph (`cargo package`, extracted-crate rebuilds, nextest, MCP inspector). -That is not required to stamp the binary assets. The battery runs on demand -only, on one free `ubuntu-latest` runner: -`gh workflow run distribution-acceptance.yml --ref master`. Nothing in this -repository runs on a timer. The battery's production binary is the CLI it -release-builds from the extracted package; it does not release-build the -source tree first (that compile is `shipped-cli` in `ci.yml`). A caller that -already holds a source-tree production binary may pass -`--reuse-release-binary` to prove that binary reports the same commit. +Rust workspace crates remain unpublished. There is no packaged-crate +reconstruction or downstream Cargo consumer acceptance path. Bazel builds +and tests the supported feature graph directly; source-package publication +requires a separate product decision with a real consumer. + +The `shipped-cli` CI job also exercises MCP stdio and the LSP initialize +handshake against its production binary in isolated temporary profiles. +Archive and MCPB verification stays attached to the exact release assets; +SDK conformance stays attached to the npm tarball. Host install, update, and uninstall journeys, and recorded native host events as historical-ingestion evidence, stay product contracts. They do diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel index ad89936906..11df536bae 100644 --- a/scripts/BUILD.bazel +++ b/scripts/BUILD.bazel @@ -53,9 +53,37 @@ py_test( main = "test-check-ambient-tokio.py", ) +py_binary( + name = "packaged_lsp_bridge", + srcs = [ + "check-packaged-lsp-bridge.py", + "lib/__init__.py", + "lib/portable_process.py", + "lib/process_control.py", + ], + imports = ["."], + main = "check-packaged-lsp-bridge.py", +) + +py_test( + name = "packaged_lsp_bridge_test", + size = "small", + srcs = [ + "check-packaged-lsp-bridge.py", + "test-check-packaged-lsp-bridge.py", + "lib/__init__.py", + "lib/portable_process.py", + "lib/process_control.py", + ], + imports = ["."], + main = "test-check-packaged-lsp-bridge.py", +) + exports_files( [ "bazel/rust_test.bzl", + "test-rust-cache-lineage.py", + "test-check-packaged-lsp-bridge.py", "test-check-sdk-publish-workflow.py", "check-sdk-publish-workflow.py", "test-build-mcpb.py", @@ -68,6 +96,7 @@ exports_files( "check-dashboard-bundle.py", "check-github-hosted-runners.py", "run-session-temporal-benchmark.sh", + "mcp-conformance-smoke.sh", "test-check-github-hosted-runners.py", "test-gen-builds-default-alias.py", "test-measure-ranked-tail-trim.py", diff --git a/scripts/bazel/rust_test.bzl b/scripts/bazel/rust_test.bzl index aa97046397..c1679468a3 100644 --- a/scripts/bazel/rust_test.bzl +++ b/scripts/bazel/rust_test.bzl @@ -106,18 +106,13 @@ def _isolated_rust_test_impl(ctx): analyzer.rustc_srcs.label.package, analyzer.rustc_srcs_path, ]) - # Distribution acceptance explicitly supplies its extracted CLI through - # --test_env. Every other runfile, and the default CLI, stays declared here. if windows: runfiles_env = "\n".join([ - ('if not defined {} '.format(variable) if variable == "TRACEDECAY_TEST_BIN" else "") + 'set "{}=%TEST_SRCDIR%/{}"'.format(variable, path) for variable, path in paths.items() ]) else: runfiles_env = " ".join([ - '"{}=${{{}:-$TEST_SRCDIR/{}}}"'.format(variable, variable, path) - if variable == "TRACEDECAY_TEST_BIN" else '"{}=$TEST_SRCDIR/{}"'.format(variable, path) for variable, path in paths.items() ]) diff --git a/scripts/check-distribution-acceptance.sh b/scripts/check-distribution-acceptance.sh deleted file mode 100755 index 9a2b686071..0000000000 --- a/scripts/check-distribution-acceptance.sh +++ /dev/null @@ -1,998 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -script_path=${BASH_SOURCE[0]} -repo=$(cd -- "$(dirname -- "$script_path")/.." && pwd -P) -reuse_release_binary="" -skip_packaged_runtime_battery=false - -usage() { - cat <<'EOF' -Usage: scripts/check-distribution-acceptance.sh [OPTIONS] - -Build and exercise the release distribution from packaged crate archives. -The gate packages every workspace crate, extracts the produced .crate archives -into an isolated temporary directory, builds the packaged CLI with -`--profile dist`, and tests the packaged library and that CLI. The -production binary this gate proves is the one it builds from the extracted -package; it never builds the source tree. - -Options: - --repo PATH Repository root (default: parent of this script) - --reuse-release-binary PATH Also prove this already-built production - binary reports the staged source commit - --skip-packaged-runtime-battery After packaging and manifest checks, skip - extracted-crate rebuilds, nextest, cargo - install, and MCP inspector dogfood; requires - --reuse-release-binary as the smoked binary - -h, --help Show this help -EOF -} - -die() { - echo "distribution acceptance: $*" >&2 - exit 1 -} - -require_command() { - command -v "$1" >/dev/null 2>&1 || die "required command not found: $1" -} - -resolve_clean_source_head() { - local source_repo=$1 - local expected_sha=${2:-} - local source_git_root - local source_git_sha - local source_drift - local first_drift - - if ! source_git_root=$(git -C "$source_repo" rev-parse --show-toplevel 2>/dev/null); then - die "source repository is not a git worktree: $source_repo" - fi - source_git_root=$(cd -- "$source_git_root" && pwd -P) - [[ $source_git_root == "$source_repo" ]] || - die "source repository must be its own git worktree: $source_repo (git reported $source_git_root)" - if ! source_git_sha=$(git -C "$source_repo" rev-parse --verify 'HEAD^{commit}' 2>/dev/null); then - die "source repository HEAD does not resolve to a commit: $source_repo" - fi - [[ ${#source_git_sha} -eq 40 && $source_git_sha != *[!0-9a-f]* ]] || - die "source repository HEAD is not a full lowercase git sha: $source_git_sha" - if [[ -n $expected_sha && $source_git_sha != "$expected_sha" ]]; then - die "source repository HEAD moved during distribution acceptance: captured $expected_sha, now $source_git_sha" - fi - # Ignored build outputs and the workflow-owned auxiliary checkout are not - # release source. All other tracked, staged, and untracked drift cannot be - # stamped as the clean release-env commit. - if ! source_drift=$(git --no-optional-locks -C "$source_repo" status \ - --porcelain=v1 \ - --untracked-files=all \ - -- . ':(exclude).release-automation' 2>/dev/null); then - die "failed to verify source repository cleanliness: $source_repo" - fi - if [[ -n $source_drift ]]; then - first_drift=${source_drift%%$'\n'*} - die "source repository has tracked or untracked drift ($first_drift); commit or remove it before distribution acceptance" - fi - - printf '%s\n' "$source_git_sha" -} - -assert_binary_source_sha() { - local binary=$1 - local product_version=$2 - local source_git_sha=$3 - local build_kind=$4 - local reported_version - local expected_version="tracedecay ${product_version}+${source_git_sha}" - - if ! reported_version=$("$binary" --version); then - die "$build_kind tracedecay binary failed to report its version: $binary" - fi - [[ $reported_version == "$expected_version" ]] || - die "$build_kind tracedecay binary reported $reported_version; expected $expected_version" -} - -assert_required_assets() { - local root_package=$1 - local cli_package=$2 - local required - local -a root_assets=( - "plugin/.lsp.json" - "plugin/.claude-plugin/plugin.json" - "plugin/.codex-plugin/plugin.json" - "plugin/.cursor-plugin/plugin.json" - "plugin/.kimi-plugin/plugin.json" - "plugin/opencode/tracedecay.ts" - "plugin/opencode/tracedecay-mcp.ts" - "plugin/opencode/opencode.registration.json" - "plugin/cursor-native-extension/embedded/extension.js" - "plugin/plugin.json" - "plugin/mcp.json" - "plugin/chatgpt-extension/embedded/server.mjs" - "plugin/chatgpt-extension/embedded/app.html" - "tests/fixtures/packaged_host_events/claude.json" - "tests/fixtures/packaged_host_events/claude/post_tool_use_write.json" - "tests/fixtures/packaged_host_events/cline-family.json" - "tests/fixtures/packaged_host_events/codex.json" - "tests/fixtures/packaged_host_events/cursor.json" - "tests/fixtures/packaged_host_events/hermes.json" - "tests/fixtures/packaged_host_events/hermes/saved-edit.json" - "tests/fixtures/packaged_host_events/hermes/terminal-receipt.json" - "tests/fixtures/packaged_host_events/kiro.json" - "tests/fixtures/packaged_host_events/kimi-code.json" - "tests/fixtures/packaged_host_events/kimi/post-tool-use-edit.json" - "tests/fixtures/packaged_host_events/opencode/baseline.json" - "tests/fixtures/packaged_host_events/pi.json" - "tests/fixtures/packaged_host_events/droid.json" - "tests/fixtures/provider_normalization/codex/session_meta.input.json" - "tests/fixtures/provider_normalization/codex/agent_message.input.json" - "tests/fixtures/analytics/codex_skill_prose.txt" - "benchmark_data/claude-observation/workload-v1.json" - ) - - for required in "${root_assets[@]}"; do - [[ -f "$root_package/$required" ]] || - die "packaged tracedecay crate is missing $required" - done - # The dashboard bundle ships in the CLI package (its build.rs embeds the - # package-local dashboard/app-dist); the root library package must not - # carry a second copy that could drift from the one the binary serves. - python3 "$repo/scripts/check-dashboard-bundle.py" \ - "$cli_package/dashboard/app-dist" - [[ ! -e "$root_package/dashboard/app-dist" ]] || - die "packaged tracedecay crate must not carry dashboard/app-dist; the CLI package owns the bundle" - - python3 - "$root_package/plugin/.lsp.json" <<'PY' -import json -import pathlib -import sys - -path = pathlib.Path(sys.argv[1]) -try: - value = json.loads(path.read_text(encoding="utf-8")) -except (OSError, json.JSONDecodeError) as error: - raise SystemExit(f"distribution acceptance: invalid packaged JSON {path}: {error}") -if not isinstance(value, dict) or not value: - raise SystemExit( - f"distribution acceptance: packaged JSON must be a non-empty object: {path}" - ) -PY - python3 "$repo/scripts/check-packaged-plugin-manifests.py" \ - "$root_package/plugin" -} - -verify_feature_wiring() { - local source_manifest=$1 - local packaged_manifest=$2 - local code_index_source_manifest=$3 - local code_index_packaged_manifest=$4 - local extraction_source_manifest=$5 - local extraction_packaged_manifest=$6 - local cli_source_manifest=$7 - local cli_packaged_manifest=$8 - local cargo_config=$9 - # Manifest and layering rules only. Per-language `cargo check` isolation is - # a source-graph property, not a packaging proof: it does not use the - # just-built release binary, and on the extracted tree it serializes one - # compile per `lang-*` feature (37 today). The feature-wiring script still - # exposes `--check-extraction-manifest` for dedicated CI. - python3 "$repo/scripts/check-distribution-feature-wiring.py" \ - --root-source "$source_manifest" \ - --root-packaged "$packaged_manifest" \ - --code-index-source "$code_index_source_manifest" \ - --code-index-packaged "$code_index_packaged_manifest" \ - --extraction-source "$extraction_source_manifest" \ - --extraction-packaged "$extraction_packaged_manifest" \ - --cli-source "$cli_source_manifest" \ - --cli-packaged "$cli_packaged_manifest" \ - --cargo-config "$cargo_config" \ - --offline -} - -read_workspace_product_version() { - python3 - "$1" <<'PY' -import sys - -# Same rule the build script applies: the one literal `version` inside -# `[workspace.package]`. An inherited or absent value is not a product version. -in_table = False -value = None -with open(sys.argv[1], encoding="utf-8") as handle: - for line in handle: - line = line.strip() - if line.startswith("#"): - continue - if line.startswith("["): - in_table = line == "[workspace.package]" - continue - if in_table and line.startswith("version"): - _, _, raw = line.partition("=") - raw = raw.strip() - if raw.startswith('"') and raw.endswith('"'): - value = raw[1:-1] -print(value or "") -PY -} - -while (($#)); do - case "$1" in - --repo) - [[ $# -ge 2 ]] || die "--repo requires a path" - repo=$2 - shift 2 - ;; - --reuse-release-binary) - [[ $# -ge 2 ]] || die "--reuse-release-binary requires a path" - reuse_release_binary=$2 - shift 2 - ;; - --skip-packaged-runtime-battery) - skip_packaged_runtime_battery=true - shift - ;; - -h|--help) - usage - exit 0 - ;; - *) - usage >&2 - die "unknown argument: $1" - ;; - esac -done - -require_command cargo -require_command cmp -require_command git -require_command python3 -require_command rustc -require_command tar - -repo=$(cd -- "$repo" && pwd -P) -[[ -f "$repo/Cargo.toml" ]] || die "Cargo.toml not found under $repo" -if [[ -n $reuse_release_binary ]]; then - [[ -e $reuse_release_binary ]] || - die "reuse-release-binary is missing: $reuse_release_binary" - reuse_release_binary=$(cd -- "$(dirname -- "$reuse_release_binary")" && pwd -P)/$(basename -- "$reuse_release_binary") - [[ -f $reuse_release_binary ]] || - die "reuse-release-binary is not a file: $reuse_release_binary" -fi -if [[ $skip_packaged_runtime_battery == true && -z $reuse_release_binary ]]; then - die "--skip-packaged-runtime-battery requires --reuse-release-binary" -fi -source_git_sha=$(resolve_clean_source_head "$repo") -for fixture in \ - claude.json \ - claude/post_tool_use_write.json \ - codex.json \ - cline-family.json \ - cursor.json \ - hermes.json \ - hermes/saved-edit.json \ - hermes/terminal-receipt.json \ - kiro.json \ - kimi-code.json \ - kimi/post-tool-use-edit.json \ - opencode/baseline.json \ - pi.json \ - droid.json; do - cmp -s \ - "$repo/crates/tracedecay-hooks/fixtures/host_events/$fixture" \ - "$repo/tests/fixtures/packaged_host_events/$fixture" || - die "packaged host-event fixture copy differs from its authority: $fixture" -done -product_version=$(read_workspace_product_version "$repo/Cargo.toml") -[[ -n $product_version ]] || - die "$repo/Cargo.toml must declare a literal version in [workspace.package]" - -work=$(mktemp -d "${TMPDIR:-/tmp}/tracedecay-distribution.XXXXXX") -trap 'rm -rf -- "$work"' EXIT - -host_target="" -while IFS= read -r rustc_version_line; do - if [[ $rustc_version_line == "host: "* ]]; then - host_target=${rustc_version_line#host: } - host_target=${host_target%$'\r'} - fi -done < <(rustc -vV) -[[ -n $host_target ]] || die "rustc did not report its host target" -release_profile_output="$work/release-profile-output" -python3 "$repo/scripts/resolve-release-source-profile.py" \ - --source "$repo" \ - --target "$host_target" \ - --github-output "$release_profile_output" -release_profile="" -release_cargo_features="" -while IFS='=' read -r output_name output_value; do - case "$output_name" in - profile) release_profile=$output_value ;; - cargo_features) release_cargo_features=$output_value ;; - esac -done < "$release_profile_output" -[[ $release_profile == production ]] || - die "distribution acceptance requires a production release profile" -[[ -n $release_cargo_features ]] || - die "production release profile omitted Cargo features" -release_cli_cargo_args=( - --no-default-features - --features "$release_cargo_features" -) - -# No source-tree release build here. The packaged CLI compiled from the -# extracted package below is the production binary this gate proves, and the -# source-tree production compile is `shipped-cli` in ci.yml. A caller that -# already holds a source-tree production binary may hand it in to prove it -# was built from this exact commit; nothing else in the battery reads it. -if [[ -n $reuse_release_binary ]]; then - echo "distribution acceptance: reusing the just-built production binary" - assert_binary_source_sha \ - "$reuse_release_binary" \ - "$product_version" \ - "$source_git_sha" \ - "reused-release" -fi - -echo "distribution acceptance: staging the product package tree" -# `tracedecay` is `crates/tracedecay`, but the assets it ships, host plugins, -# vendored payloads, benchmark corpora, and the packaged fixtures, are -# repository-root directories shared with the whole workspace. Cargo packs -# only what lives inside the package directory, so packaging the checkout -# as-is yields a `tracedecay` archive with none of them, and the `include` -# whitelist in `crates/tracedecay/Cargo.toml` silently matches nothing. Stage -# a copy of the workspace, place exactly those root assets beside the product -# manifest, and pack from the staged tree. The dashboard bundle is different: -# it ships in the CLI package (`crates/tracedecay-cli`, whose `include` names -# `/dashboard/app-dist/**`), and its build.rs embeds a package-local -# `dashboard/app-dist` when one exists, so the bundle is staged beside the -# CLI manifest below rather than beside the library manifest. -staged="$work/staged" -mkdir -p -- "$staged" -resolve_clean_source_head "$repo" "$source_git_sha" >/dev/null -tar -C "$repo" \ - --exclude=./target \ - --exclude=./.git \ - --exclude=./.codex-worktrees \ - --exclude=./.claude-worktrees \ - --exclude=node_modules \ - --exclude=.pnpm \ - -cf - . | tar -xf - -C "$staged" -resolve_clean_source_head "$repo" "$source_git_sha" >/dev/null - -staged_product="$staged/crates/tracedecay" -[[ -f "$staged_product/Cargo.toml" ]] || - die "staged workspace is missing the product manifest at crates/tracedecay" - -# Exactly the root entries the product `include` whitelist names. Keep this in -# sync with `include` in crates/tracedecay/Cargo.toml: a missing asset is a -# packaging defect, never something to skip past. -declare -a staged_root_assets=( - "CHANGELOG.md" - "LICENSE" - "README.md" - "rust-toolchain.toml" - ".cargo/config.toml" - "plugin" - "vendor" - "benchmark_data" - "dashboard/hermes-wrapper" - "tests/fixtures" - "scripts/run-session-temporal-benchmark.sh" -) -# A package directory may already carry its own entry at the destination path, -# as `crates/tracedecay/tests/fixtures` does. `cp -a` merges a directory into -# an existing directory of the same name, which would leave the package-local -# asset a superset of the root one. Clear the destination so the staged asset -# is exactly the root snapshot the assertion below demands. -for asset in "${staged_root_assets[@]}"; do - [[ -e "$staged/$asset" ]] || - die "product package asset is missing from the staged source tree: $asset" - mkdir -p -- "$staged_product/$(dirname -- "$asset")" - rm -rf -- "$staged_product/$asset" - cp -a -- "$staged/$asset" "$staged_product/$(dirname -- "$asset")/" -done - -# The CLI package carries the prebuilt dashboard bundle; its `include` -# whitelist names `/dashboard/app-dist/**` and its build.rs embeds the -# package-local copy in packaged-crate mode. -staged_cli_crate="$staged/crates/tracedecay-cli" -[[ -f "$staged_cli_crate/Cargo.toml" ]] || - die "staged workspace is missing the CLI manifest at crates/tracedecay-cli" -declare -a staged_cli_assets=( - "dashboard/app-dist" -) -for asset in "${staged_cli_assets[@]}"; do - [[ -e "$staged/$asset" ]] || - die "CLI package asset is missing from the staged source tree: $asset" - mkdir -p -- "$staged_cli_crate/$(dirname -- "$asset")" - rm -rf -- "$staged_cli_crate/$asset" - cp -a -- "$staged/$asset" "$staged_cli_crate/$(dirname -- "$asset")/" -done - -# The manifest reads its readme from the workspace root, which is outside the -# package. The staged tree carries its own copy, so point at that one; nothing -# else about the staged manifest may differ, and the feature-wiring comparison -# below is run against the source manifest to prove it. -python3 - "$staged_product/Cargo.toml" <<'PY' -import pathlib -import sys - -path = pathlib.Path(sys.argv[1]) -text = path.read_text(encoding="utf-8") -old = 'readme = "../../README.md"' -new = 'readme = "README.md"' -if text.count(old) != 1: - raise SystemExit( - "distribution acceptance: staged product manifest does not declare the " - f"expected workspace-root readme ({old})" - ) -path.write_text(text.replace(old, new), encoding="utf-8") -PY - -# Prove the package-local copies are byte-identical to the validated staged -# snapshot, so no later live-checkout change can enter the archive. -assert_staged_assets_identical() { - local source_dir=$1 - local staged_dir=$2 - shift 2 - python3 - "$source_dir" "$staged_dir" "$@" <<'PY' -import hashlib -import pathlib -import sys - -source_root = pathlib.Path(sys.argv[1]) -staged = pathlib.Path(sys.argv[2]) - - -def digest(root: pathlib.Path) -> str: - if root.is_file(): - return hashlib.sha256(root.read_bytes()).hexdigest() - accumulator = hashlib.sha256() - for path in sorted(p for p in root.rglob("*") if p.is_file()): - accumulator.update(str(path.relative_to(root)).encode("utf-8")) - accumulator.update(path.read_bytes()) - return accumulator.hexdigest() - - -for asset in sys.argv[3:]: - source, copied = source_root / asset, staged / asset - if not copied.exists(): - raise SystemExit(f"distribution acceptance: staged asset is missing: {asset}") - if digest(source) != digest(copied): - raise SystemExit( - f"distribution acceptance: staged asset differs from its snapshot: {asset}" - ) -print(f"distribution acceptance: staged {len(sys.argv) - 3} package asset entries") -PY -} -assert_staged_assets_identical "$staged" "$staged_product" "${staged_root_assets[@]}" -assert_staged_assets_identical "$staged" "$staged_cli_crate" "${staged_cli_assets[@]}" - -echo "distribution acceptance: packaging every workspace crate" -# Every workspace member is publish = false: releases ship GitHub-release -# artifacts, never crates.io uploads. Packaging is used here only to produce -# the per-crate source trees the acceptance battery runs against, so skip the -# per-package lockfile: generating it would resolve the unpublished internal -# dependencies against crates.io and fail. Nothing downstream reads the -# embedded lock, every extracted tree resolves through the [patch.crates-io] -# path overlay below, and the install step builds from a path, not an archive. -cargo package \ - --manifest-path "$staged/Cargo.toml" \ - --workspace \ - --allow-dirty \ - --no-verify \ - --exclude-lockfile - -metadata="$work/metadata.json" -cargo metadata \ - --manifest-path "$staged/Cargo.toml" \ - --format-version 1 \ - --no-deps >"$metadata" - -target_directory=$(python3 - "$metadata" <<'PY' -import json -import sys - -with open(sys.argv[1], encoding="utf-8") as handle: - print(json.load(handle)["target_directory"]) -PY -) - -package_table="$work/packages.tsv" -python3 - "$metadata" >"$package_table" <<'PY' -import json -import sys - -with open(sys.argv[1], encoding="utf-8") as handle: - metadata = json.load(handle) -members = set(metadata["workspace_members"]) -for package in metadata["packages"]: - if package["id"] in members: - print(f'{package["name"]}\t{package["version"]}') -PY - -# `tracedecay-agent-hosts` stamps the product version, and that value now lives -# in `[workspace.package]` at the workspace root, two directories above each -# crate. The sibling symlinks below already reproduce the `crates/` shape -# a build script sees in the repository; this reproduces the workspace root -# that sits above it, so the battery resolves the same version the repository -# build does instead of failing closed on a missing manifest. `exclude` keeps -# every extracted tree a standalone build rather than a member of this stub. -package_root="$work/packages" -packages="$package_root/crates" -mkdir -p -- "$packages" -cat >"$package_root/Cargo.toml" <`, so the stub root has to present -# the same trees. They are linked from the staged copy rather than the live -# checkout so the battery reads exactly what was packaged. -for root_asset in plugin tests dashboard benchmark_data; do - [[ -e "$staged/$root_asset" ]] || - die "staged workspace is missing the root asset tree $root_asset" - ln -sfn -- "$staged/$root_asset" "$package_root/$root_asset" -done -declare -A package_dirs=() -while IFS=$'\t' read -r name version; do - archive="$target_directory/package/$name-$version.crate" - [[ -f "$archive" ]] || - die "cargo package did not produce $archive" - tar -xzf "$archive" -C "$packages" - directory="$packages/$name-$version" - [[ -f "$directory/Cargo.toml" ]] || - die "package archive did not contain $name-$version/Cargo.toml" - package_dirs["$name"]=$directory - # Each extracted tree is its own Cargo root, so without a lockfile Cargo - # would re-resolve every registry dependency. Offline that refuses a - # version crates.io has yanked since the workspace locked it (bisync 0.3.0 - # under gix-protocol did exactly this). The checked-in workspace lockfile is - # the release resolution; every packaged crate builds against it. - cp -- "$staged/Cargo.lock" "$directory/Cargo.lock" - # Extracted archives are named `-`, but in the workspace every - # crate sits at `crates/`. Sources that reach a sibling crate by - # relative `#[path]`, e.g. `tracedecay/src/daemon.rs` includes scheduler - # test modules from `tracedecay-code-index-runtime`, resolve against that - # unversioned shape. `cargo package` cannot carry a file from outside the - # package, so give the battery the same sibling layout the workspace has - # rather than a copy that could drift from it. - ln -sfn -- "$name-$version" "$packages/$name" -done <"$package_table" - -for required_package in \ - tracedecay \ - tracedecay-cli \ - tracedecay-agent-hosts \ - tracedecay-contracts \ - tracedecay-api \ - tracedecay-tool-catalog \ - tracedecay-code-index \ - tracedecay-code-index-runtime \ - tracedecay-code-extraction; do - [[ -n ${package_dirs[$required_package]:-} ]] || - die "workspace package required by the distribution gate was not produced: $required_package" -done -root_package=${package_dirs[tracedecay]} -cli_package=${package_dirs[tracedecay-cli]} -agent_hosts_package=${package_dirs[tracedecay-agent-hosts]} -code_index_package=${package_dirs[tracedecay-code-index]} -code_extraction_package=${package_dirs[tracedecay-code-extraction]} -catalog_package=${package_dirs[tracedecay-tool-catalog]} -contracts_package=${package_dirs[tracedecay-contracts]} - -assert_required_assets "$root_package" "$cli_package" -[[ ! -e "$package_root/.git" && ! -L "$package_root/.git" ]] || - die "extracted package workspace unexpectedly contains .git metadata: $package_root/.git" - -patch_config="$work/packaged-crates.toml" -python3 - "$metadata" "$packages" "$staged/Cargo.toml" >"$patch_config" <<'PY' -import json -import pathlib -import sys -import tomllib - -with open(sys.argv[1], encoding="utf-8") as handle: - metadata = json.load(handle) -members = set(metadata["workspace_members"]) -packages = pathlib.Path(sys.argv[2]) -with open(sys.argv[3], "rb") as handle: - workspace_manifest = tomllib.load(handle) -print("[patch.crates-io]") -for package in sorted(metadata["packages"], key=lambda value: value["name"]): - if package["id"] not in members: - continue - path = packages / f'{package["name"]}-{package["version"]}' - print(f'{json.dumps(package["name"])} = {{ path = {json.dumps(str(path))} }}') -# Packaged crates resolve outside the workspace, so the workspace manifest's -# patches must be re-applied here for the extracted trees to see the same -# patched dependencies: git patches (e.g. the pinned tree-sitter-rust fork) -# verbatim, and path patches against the -# staged workspace root, which carries the same `vendor` tree. -staged_root = pathlib.Path(sys.argv[3]).parent -for name, spec in workspace_manifest.get("patch", {}).get("crates-io", {}).items(): - if not isinstance(spec, dict): - continue - if "git" in spec: - entry = f'{json.dumps(name)} = {{ git = {json.dumps(spec["git"])}' - for key in ("rev", "branch", "tag"): - if key in spec: - entry += f", {key} = {json.dumps(spec[key])}" - entry += " }" - print(entry) - elif "path" in spec: - patched = staged_root / spec["path"] - if not (patched / "Cargo.toml").is_file(): - raise SystemExit( - f"distribution acceptance: workspace path patch {name} is not staged at {patched}" - ) - print(f'{json.dumps(name)} = {{ path = {json.dumps(str(patched))} }}') -# Extracted packages do not see workspace Cargo.toml profiles. Copy -# [profile.dist] from that manifest so `--profile dist` matches shipping. -dist_profile = workspace_manifest.get("profile", {}).get("dist") -if not isinstance(dist_profile, dict): - raise SystemExit( - "distribution acceptance: workspace Cargo.toml omitted [profile.dist]" - ) -required = ("inherits", "lto", "codegen-units", "strip") -missing = [key for key in required if key not in dist_profile] -if missing: - raise SystemExit( - "distribution acceptance: [profile.dist] missing " + ", ".join(missing) - ) -print() -print("[profile.dist]") -for key, value in dist_profile.items(): - if isinstance(value, bool): - rendered = "true" if value else "false" - elif isinstance(value, int): - rendered = str(value) - elif isinstance(value, str): - rendered = json.dumps(value) - else: - raise SystemExit( - f"distribution acceptance: [profile.dist] {key} has unsupported type" - ) - print(f"{key} = {rendered}") -PY - -verify_feature_wiring \ - "$repo/crates/tracedecay/Cargo.toml" \ - "$root_package/Cargo.toml" \ - "$repo/crates/tracedecay-code-index/Cargo.toml" \ - "$code_index_package/Cargo.toml" \ - "$repo/crates/tracedecay-code-extraction/Cargo.toml" \ - "$code_extraction_package/Cargo.toml" \ - "$repo/crates/tracedecay-cli/Cargo.toml" \ - "$cli_package/Cargo.toml" \ - "$patch_config" - -if [[ $skip_packaged_runtime_battery == true ]]; then - echo "distribution acceptance: skipping packaged runtime battery" - "$reuse_release_binary" --help >/dev/null || - die "reused release binary failed --help: $reuse_release_binary" - echo "distribution acceptance passed" - exit 0 -fi - -echo "distribution acceptance: compiling packaged CLI with dist profile" -# Checkout rustflags skip LLVM bitcode for non-LTO graphs. A process -# RUSTFLAGS replaces those flags; thin LTO needs the bitcode back. -TRACEDECAY_RELEASE_GIT_SHA="$source_git_sha" \ - RUSTFLAGS="${RUSTFLAGS:+$RUSTFLAGS }-C embed-bitcode=yes" \ - cargo build \ - --manifest-path "$cli_package/Cargo.toml" \ - --profile dist \ - "${release_cli_cargo_args[@]}" \ - --bin tracedecay \ - --config "$patch_config" -executable_suffix="" -if [[ ${OS:-} == "Windows_NT" ]]; then - executable_suffix=".exe" -fi -packaged_cli_bin="$target_directory/dist/tracedecay${executable_suffix}" -[[ -x $packaged_cli_bin ]] || - die "packaged tracedecay CLI build did not produce $packaged_cli_bin" -assert_binary_source_sha \ - "$packaged_cli_bin" \ - "$product_version" \ - "$source_git_sha" \ - "extracted-package" - -echo "distribution acceptance: testing packaged patched Rust grammar" -cargo nextest run \ - --manifest-path "$code_extraction_package/Cargo.toml" \ - --all-features \ - --config "$patch_config" \ - --test main \ - -E 'test(/^rust::/)' \ - --no-tests=fail - -# The extracted CLI build above compiles the complete packaged production -# graph. Query, root-library, and LSP source suites run in exhaustive Linux CI; -# rerunning them here added no archive assertion. The MCP harness stays focused -# on the modules that spawn TRACEDECAY_TEST_BIN, so it proves the packaged CLI -# without rerunning unrelated source-library tests. Run it from the verified -# checkout through the canonical isolated Bazel launcher. -echo "distribution acceptance: checking packaged CLI integration behavior" -resolve_clean_source_head "$repo" "$source_git_sha" >/dev/null -# The extracted binary is outside Bazel's declared runfiles. Run locally and -# disable result caching so its current bytes are exercised on every invocation. -# Check each filter independently: libtest otherwise accepts zero matches. -require_command bazel -for filter in \ - 'mcp_cli_serve_test::' \ - 'serve_template_path_test::' \ - 'mcp_handler_test::lcm_test::lcm_status_cli_bridge_accepts_json_args'; do - ( - cd "$repo" - test_args=(--test_arg="$filter") - expected_count=nonzero - if [[ $filter == mcp_handler_test::* ]]; then - test_args+=(--test_arg=--exact) - expected_count=1 - fi - REQUIRE_EXACT_TEST_COUNT="$expected_count" scripts/require-exact-test.sh \ - bazel test --config=ci //crates/tracedecay:mcp_suite \ - --test_strategy=standalone \ - --nocache_test_results \ - --test_output=all \ - --test_env="TRACEDECAY_TEST_BIN=$packaged_cli_bin" \ - "${test_args[@]}" - ) -done -resolve_clean_source_head "$repo" "$source_git_sha" >/dev/null - -install_root="$work/install" -echo "distribution acceptance: staging the packaged CLI as the installed binary" -# `cargo install --path` rebuilds the same extracted CLI we just compiled. -# Copy that artifact into the cargo-install layout so later MCP/LSP checks -# exercise the packaged binary without a third dist compile. -mkdir -p -- "$install_root/bin" -cp -- "$packaged_cli_bin" \ - "$install_root/bin/tracedecay${executable_suffix}" -chmod +x "$install_root/bin/tracedecay${executable_suffix}" - -consumer="$work/library-consumer" -mkdir -p -- "$consumer/src" -python3 - "$root_package/Cargo.toml" "$root_package" "$catalog_package" "$agent_hosts_package" \ - "$contracts_package" \ - >"$consumer/Cargo.toml" <<'PY' -import json -import sys -import tomllib -from pathlib import Path - -with Path(sys.argv[1]).open("rb") as handle: - manifest = tomllib.load(handle) -if "production" not in manifest.get("features", {}): - raise SystemExit("packaged tracedecay manifest omitted the production feature") -# The consumer proves the packaged catalog composes and the host bundles -# verify; neither depends on optimization. Its feature unification differs -# from the packaged CLI's (the CLI's own dependencies light extra features on -# mio, hyper, tower, ...), so under --release it recompiled the whole -# workspace spine: 18m measured on ubuntu-latest for a crate that itself -# compiles in seconds. Unoptimized and without debuginfo the same graph -# compiles in ~3m on the same hardware. -print("""[package] -name = "tracedecay-distribution-consumer" -version = "0.0.0" -edition = "2024" - -[profile.dev] -opt-level = 0 -debug = 0 - -[dependencies]""") -print( - "tracedecay = { path = " - + json.dumps(sys.argv[2]) - + ', default-features = false, features = ["production"] }' -) -print( - "tracedecay-tool-catalog = { path = " - + json.dumps(sys.argv[3]) - + " }" -) -print( - "tracedecay-agent-hosts = { path = " - + json.dumps(sys.argv[4]) - + " }" -) -print( - "tracedecay-contracts = { path = " - + json.dumps(sys.argv[5]) - + " }" -) -PY -# The host bundle generators sign each bundle with the commit that produced -# it; the packaged product's source head is that commit. -printf 'const GENERATOR_COMMIT: &str = "%s";\n' "$source_git_sha" >"$consumer/src/main.rs" -cat >>"$consumer/src/main.rs" <<'RS' -use std::collections::BTreeSet; - -use tracedecay_contracts::catalog_composition::build_application_catalog_snapshot; -use tracedecay_agent_hosts::agents::host_bundle_registry::{ - RECEIPT_BACKED_HOST_KINDS, default_components, verified_embedded_default_host_component_set, - verified_embedded_host_bundle, -}; -use tracedecay_agent_hosts::agents::host_bundle::stock_host_kinds; -use tracedecay_tool_catalog::{AvailabilityContract, CapabilityId}; - -const REQUIRED_CAPABILITIES: [&str; 10] = [ - "capability.application.feedback.diagnostics", - "capability.application.feedback.get", - "capability.application.feedback.expand", - "capability.application.feedback.list", - "capability.application.feedback.impact", - "capability.application.feedback.affected-tests", - "capability.application.feedback.test-results", - "capability.application.feedback.github-review-ingest", - "capability.application.feedback.ci-failure-localize", - "capability.application.feedback.proximity", -]; - -fn main() { - let snapshot = build_application_catalog_snapshot() - .expect("packaged application catalog must compose"); - for raw_id in REQUIRED_CAPABILITIES { - let id = CapabilityId::new(raw_id).expect("required capability ID must be valid"); - let capability = snapshot - .capability(&id) - .unwrap_or_else(|| panic!("packaged catalog omitted {raw_id}")); - assert!( - matches!(capability.availability(), AvailabilityContract::Available), - "packaged catalog capability is not callable: {raw_id}" - ); - } - - let supported_hosts = stock_host_kinds() - .into_iter() - .filter(|host| !default_components(*host).is_empty()) - .collect::>(); - let receipt_backed_hosts = RECEIPT_BACKED_HOST_KINDS - .into_iter() - .collect::>(); - assert_eq!( - supported_hosts, - receipt_backed_hosts, - "packaged default host bundle inventory changed" - ); - for host in RECEIPT_BACKED_HOST_KINDS { - let components = default_components(host); - let component_set = verified_embedded_default_host_component_set(host, 0, GENERATOR_COMMIT) - .expect("default packaged host component set must verify"); - assert_eq!(component_set.component_set.components.len(), components.len()); - for component in components { - let bundle = verified_embedded_host_bundle(host, component, 0, GENERATOR_COMMIT) - .expect("packaged host bundle must be callable"); - bundle - .manifest - .validate_structure() - .expect("packaged host bundle manifest must validate"); - assert!(!bundle.contents.is_empty(), "packaged host bundle has no assets"); - } - } -} -RS - -# Deliberately no lockfile. Unlike the extracted packages, which are each -# their own Cargo root and need the release resolution, this consumer is a -# downstream crate that has never seen our lockfile. Letting it resolve from -# scratch is the only check that the published dependency set is resolvable -# at all, which is what caught the yanked bisync 0.3.0 under gix-protocol. -echo "distribution acceptance: calling packaged catalog and host bundles" -CARGO_NET_OFFLINE=true cargo run \ - --manifest-path "$consumer/Cargo.toml" \ - --bin tracedecay-distribution-consumer \ - --config "$patch_config" - -test_api_probe="$work/test-api-probe" -mkdir -p -- "$test_api_probe/src" -cat >"$test_api_probe/Cargo.toml" <"$test_api_probe/src/main.rs" <<'RS' -use tracedecay::mcp::McpServer; - -fn main() { - let _ = McpServer::has_project_session_retrieval_service_for_test; -} -RS -cp -- "$staged/Cargo.lock" "$test_api_probe/Cargo.lock" -echo "distribution acceptance: proving production package omits test APIs" -test_api_stderr="$work/test-api-probe.stderr" -# Same production features and lockfile as the packaged CLI dist build, -# which already compiled this graph into the shared dist directory (repo -# `target-dir`). Only the probe itself is compiled. A dev-profile check paid -# a second metadata compile of the whole graph. The expected refusal is still -# E0599 on the test-only associated item. -if CARGO_NET_OFFLINE=true \ - RUSTFLAGS="${RUSTFLAGS:+$RUSTFLAGS }-C embed-bitcode=yes" \ - cargo check \ - --manifest-path "$test_api_probe/Cargo.toml" \ - --profile dist \ - --config "$patch_config" \ - 2>"$test_api_stderr"; then - die "production package exposed test-transport APIs" -fi -# rustc words this refusal differently across releases ("no function or -# associated item named" before 1.97, "no associated function or constant -# named" from 1.97), so match the error code and the probed name. -grep -Eq "error\[E0599\].*has_project_session_retrieval_service_for_test" \ - "$test_api_stderr" || { - echo "distribution acceptance: test API probe stderr follows" >&2 - tail -n 60 -- "$test_api_stderr" >&2 - die "test API probe failed for an unexpected reason" -} - -binary=$(python3 "$repo/scripts/resolve-installed-binary.py" \ - "$install_root" \ - "${RUNNER_OS:-}") -assert_binary_source_sha \ - "$binary" \ - "$product_version" \ - "$source_git_sha" \ - "installed extracted-package" - -echo "distribution acceptance: exercising installed MCP behavior" -if [[ ${RUNNER_OS:-} == Windows ]]; then - python3 "$repo/scripts/check-packaged-mcp-stdio.py" \ - "$binary" \ - "$work/mcp-stdio" -else - TRACEDECAY_BIN="$binary" "$repo/scripts/mcp-conformance-smoke.sh" -fi - -lsp_servers="$work/lsp-servers.json" -"$binary" lsp servers --json >"$lsp_servers" -python3 - "$lsp_servers" <<'PY' -import json -import sys -from pathlib import Path - -inventory = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) -if not isinstance(inventory, dict) or inventory.get("resolution") not in {"daemon", "cli_path"}: - raise SystemExit("distribution acceptance: lsp servers omitted its availability resolution") -value = inventory.get("servers") -if not isinstance(value, list) or not value: - raise SystemExit("distribution acceptance: lsp servers returned an empty inventory") -required_languages = {"rust", "typescript", "javascript", "python", "go", "c", "cpp"} -languages = set() -for server in value: - if ( - not isinstance(server, dict) - or not isinstance(server.get("language"), str) - or not isinstance(server.get("language_id"), str) - or not isinstance(server.get("command"), str) - or not isinstance(server.get("available"), bool) - or not isinstance(server.get("extensions"), list) - or not server["extensions"] - ): - raise SystemExit("distribution acceptance: invalid lsp server entry") - languages.add(server["language"]) -missing = sorted(required_languages - languages) -if missing: - raise SystemExit( - "distribution acceptance: lsp inventory omitted " + ", ".join(missing) - ) -PY - -python3 "$repo/scripts/check-packaged-lsp-bridge.py" \ - "$binary" \ - "$work/lsp-bridge" - -echo "distribution acceptance passed" diff --git a/scripts/check-distribution-feature-wiring.py b/scripts/check-distribution-feature-wiring.py deleted file mode 100644 index 7e4245f94c..0000000000 --- a/scripts/check-distribution-feature-wiring.py +++ /dev/null @@ -1,261 +0,0 @@ -#!/usr/bin/env python3 -"""Validate source and packaged Cargo feature ownership for distribution builds. - -The checks are public-contract and layering rules only: the packaged manifest -must carry the source feature set, optional native dependencies must stay -optional and feature-wired, and the supported `lang-*` surface must match the -extraction owner. How a feature is forwarded through intermediate crates is -Cargo's job; resolved behavior is proven by the packaged-artifact builds and -launch checks in check-distribution-acceptance.sh. Per-language isolation -compiles remain available via `--check-extraction-manifest` and are not -part of the release acceptance gate. -""" - -from __future__ import annotations - -import argparse -from pathlib import Path -import subprocess -import tomllib - - -REQUIRED_ROOT_FEATURES = { - "full", - "token-counting", - "test-transport", -} -REQUIRED_CLI_FEATURE_MEMBERS = { - "production": {"tracedecay/production"}, -} - - -def load(path: Path) -> dict: - with path.open("rb") as handle: - return tomllib.load(handle) - - -def optional_dependencies(manifest: dict) -> set[str]: - names: set[str] = set() - - def collect(table: object) -> None: - if not isinstance(table, dict): - return - dependencies = table.get("dependencies") - if isinstance(dependencies, dict): - for name, spec in dependencies.items(): - if isinstance(spec, dict) and spec.get("optional") is True: - names.add(name) - - collect(manifest) - for target in manifest.get("target", {}).values(): - collect(target) - return names - - -def require_matching_features(name: str, source: dict, packaged: dict) -> dict: - source_features = source.get("features", {}) - packaged_features = packaged.get("features", {}) - if source_features != packaged_features: - raise SystemExit( - f"distribution acceptance: packaged {name} feature wiring differs from Cargo.toml" - ) - return packaged_features - - -def require_optional_dependencies_wired(name: str, manifest: dict, features: dict) -> None: - references = { - item - for members in features.values() - for item in members - if isinstance(item, str) - } - unwired = sorted( - dependency - for dependency in optional_dependencies(manifest) - if f"dep:{dependency}" not in references and dependency not in features - ) - if unwired: - raise SystemExit( - f"distribution acceptance: {name} optional dependencies are not feature-wired: " - + ", ".join(unwired) - ) - - -def language_feature_names(features: dict) -> set[str]: - return {name for name in features if name.startswith("lang-")} - - -def require_language_surface( - name: str, - features: dict, - authority_features: set[str], -) -> None: - """The public `lang-*` surface must advertise exactly the owner's languages.""" - actual_features = language_feature_names(features) - if actual_features != authority_features: - missing = sorted(authority_features - actual_features) - extra = sorted(actual_features - authority_features) - details = [] - if missing: - details.append("missing " + ", ".join(missing)) - if extra: - details.append("extra " + ", ".join(extra)) - raise SystemExit( - f"distribution acceptance: {name} language features differ from " - "tracedecay-code-extraction: " + "; ".join(details) - ) - - -def require_isolated_language_features_compile( - manifest_path: Path, - authority_features: set[str], - cargo_config: Path | None, - offline: bool, -) -> None: - manifest = load(manifest_path) - package_name = manifest.get("package", {}).get("name") - if not isinstance(package_name, str): - raise SystemExit( - "distribution acceptance: extraction build manifest has no package name" - ) - - build_features = language_feature_names(manifest.get("features", {})) - if build_features != authority_features: - raise SystemExit( - "distribution acceptance: extraction build manifest language features " - "differ from the packaged authority" - ) - - for feature in sorted(authority_features): - command = [ - "cargo", - "check", - "--manifest-path", - str(manifest_path), - "--package", - package_name, - "--lib", - "--no-default-features", - "--features", - feature, - ] - if cargo_config is not None: - command.extend(["--config", str(cargo_config)]) - if offline: - command.append("--offline") - completed = subprocess.run( - command, check=False, capture_output=True, text=True - ) - if completed.returncode != 0: - details = completed.stderr.strip() or completed.stdout.strip() - raise SystemExit( - f"distribution acceptance: {feature} does not compile in isolation" - + (f"\n{details}" if details else "") - ) - - -def validate( - root_source: dict, - root_packaged: dict, - code_index_source: dict, - code_index_packaged: dict, - extraction_source: dict, - extraction_packaged: dict, - cli_source: dict, - cli_packaged: dict, -) -> None: - root_features = require_matching_features("root", root_source, root_packaged) - missing = sorted(REQUIRED_ROOT_FEATURES - root_features.keys()) - if missing: - raise SystemExit( - "distribution acceptance: source manifest is missing required features: " - + ", ".join(missing) - ) - require_optional_dependencies_wired("root", root_packaged, root_features) - - require_matching_features( - "tracedecay-code-index", code_index_source, code_index_packaged - ) - extraction_features = require_matching_features( - "tracedecay-code-extraction", extraction_source, extraction_packaged - ) - require_language_surface( - "root", root_features, language_feature_names(extraction_features) - ) - require_optional_dependencies_wired( - "tracedecay-code-extraction", extraction_packaged, extraction_features - ) - - cli_features = require_matching_features( - "tracedecay-cli", cli_source, cli_packaged - ) - missing_cli = sorted( - REQUIRED_CLI_FEATURE_MEMBERS.keys() - cli_features.keys() - ) - if missing_cli: - raise SystemExit( - "distribution acceptance: tracedecay-cli is missing required features: " - + ", ".join(missing_cli) - ) - for feature, expected in REQUIRED_CLI_FEATURE_MEMBERS.items(): - members = cli_features.get(feature) - # Extra crate passthroughs are allowed; the contract is the required - # release members, not an exhaustive crate inventory. - if not isinstance(members, list) or not expected.issubset(members): - raise SystemExit( - f"distribution acceptance: tracedecay-cli {feature} must enable " - + ", ".join(sorted(expected)) - ) - require_optional_dependencies_wired( - "tracedecay-cli", cli_packaged, cli_features - ) - - -def main() -> int: - repo = Path(__file__).resolve().parent.parent - root_manifest = repo / "crates/tracedecay/Cargo.toml" - code_index_manifest = repo / "crates/tracedecay-code-index/Cargo.toml" - extraction_manifest = repo / "crates/tracedecay-code-extraction/Cargo.toml" - cli_manifest = repo / "crates/tracedecay-cli/Cargo.toml" - parser = argparse.ArgumentParser( - description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter - ) - # Source manifests default to this checkout; packaged manifests must be - # the extracted `.crate` trees, so they have no default, comparing a - # manifest with itself is not package verification. - parser.add_argument("--root-source", type=Path, default=root_manifest) - parser.add_argument("--root-packaged", type=Path, required=True) - parser.add_argument("--code-index-source", type=Path, default=code_index_manifest) - parser.add_argument("--code-index-packaged", type=Path, required=True) - parser.add_argument("--extraction-source", type=Path, default=extraction_manifest) - parser.add_argument("--extraction-packaged", type=Path, required=True) - parser.add_argument("--cli-source", type=Path, default=cli_manifest) - parser.add_argument("--cli-packaged", type=Path, required=True) - parser.add_argument("--check-extraction-manifest", type=Path) - parser.add_argument("--cargo-config", type=Path) - parser.add_argument("--offline", action="store_true") - arguments = parser.parse_args() - extraction_packaged = load(arguments.extraction_packaged) - validate( - load(arguments.root_source), - load(arguments.root_packaged), - load(arguments.code_index_source), - load(arguments.code_index_packaged), - load(arguments.extraction_source), - extraction_packaged, - load(arguments.cli_source), - load(arguments.cli_packaged), - ) - if arguments.check_extraction_manifest is not None: - require_isolated_language_features_compile( - arguments.check_extraction_manifest, - language_feature_names(extraction_packaged.get("features", {})), - arguments.cargo_config, - arguments.offline, - ) - print("distribution feature wiring is valid") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/check-packaged-mcp-stdio.py b/scripts/check-packaged-mcp-stdio.py deleted file mode 100755 index a935f10acb..0000000000 --- a/scripts/check-packaged-mcp-stdio.py +++ /dev/null @@ -1,345 +0,0 @@ -#!/usr/bin/env python3 -"""Cross-platform installed-binary MCP stdio transport acceptance.""" - -from __future__ import annotations - -import json -import os -from pathlib import Path -import shutil -import subprocess -import sys -import time - -from lib.process_control import terminate -from lib.portable_process import dies_with_this_process - - -# Default MCP Inspector pin, shared with scripts/mcp-conformance-smoke.sh -# via scripts/lib/inspector_version, override both through INSPECTOR_VERSION. -INSPECTOR_VERSION = os.environ.get( - "INSPECTOR_VERSION", - Path(__file__) - .resolve() - .with_name("lib") - .joinpath("inspector_version") - .read_text(encoding="utf-8") - .strip(), -) -REQUIRED_TOOLS = { - "tracedecay_search", - "tracedecay_diagnostics", - "tracedecay_impact", - "tracedecay_affected", - "tracedecay_test_map", -} - - -def run( - arguments: list[str], - *, - cwd: Path, - environment: dict[str, str], - check: bool = True, -) -> subprocess.CompletedProcess[str]: - return subprocess.run( - arguments, - cwd=cwd, - env=environment, - check=check, - capture_output=True, - text=True, - timeout=60, - ) - - -def inspect( - npx: str, - binary: Path, - fixture: Path, - environment: dict[str, str], - *arguments: str, - check: bool = True, -) -> subprocess.CompletedProcess[str]: - return run( - [ - npx, - "-y", - f"@modelcontextprotocol/inspector@{INSPECTOR_VERSION}", - "--cli", - str(binary), - "serve", - "-p", - str(fixture), - *arguments, - ], - cwd=fixture, - environment=environment, - check=check, - ) - - -def daemon_command( - binary: Path, socket_path: Path, *, platform_name: str = os.name -) -> list[str]: - command = [str(binary), "daemon", "run"] - if platform_name != "nt": - command.extend(["--socket", str(socket_path)]) - return command - - -def daemon_environment( - environment: dict[str, str], - socket_path: Path, - *, - platform_name: str = os.name, -) -> dict[str, str]: - configured = environment.copy() - configured.pop("TRACEDECAY_DAEMON_SOCKET", None) - if platform_name != "nt": - configured["TRACEDECAY_DAEMON_SOCKET"] = str(socket_path) - return configured - - -def daemon_endpoint_is_ready(returncode: int, stdout: str) -> bool: - return returncode == 0 and "(connectable)" in stdout - - -def wait_for_daemon( - daemon: subprocess.Popen[bytes], - binary: Path, - fixture: Path, - environment: dict[str, str], -) -> None: - deadline = time.monotonic() + 15 - while time.monotonic() < deadline: - if daemon.poll() is not None: - raise SystemExit( - f"packaged daemon exited before init with {daemon.returncode}" - ) - status = run( - [str(binary), "daemon", "status"], - cwd=fixture, - environment=environment, - check=False, - ) - if daemon_endpoint_is_ready(status.returncode, status.stdout): - return - time.sleep(0.05) - raise SystemExit("packaged daemon endpoint did not become ready") - - -def problem_of(payload: dict) -> dict: - """The typed problem a tool-result error carries in structuredContent.""" - structured = payload.get("structuredContent") - problem = structured.get("problem") if isinstance(structured, dict) else None - return problem if isinstance(problem, dict) else {} - - -def main() -> int: - if len(sys.argv) != 3: - raise SystemExit( - "usage: check-packaged-mcp-stdio.py TRACEDecay_BINARY WORK_DIRECTORY" - ) - binary = Path(sys.argv[1]).resolve() - work = Path(sys.argv[2]).resolve() - npx = shutil.which("npx") - if not binary.is_file(): - raise SystemExit(f"installed tracedecay binary is missing: {binary}") - if npx is None: - raise SystemExit("npx is required for MCP stdio acceptance") - - fixture = work / "project" - home = work / "home" - fixture.joinpath("src").mkdir(parents=True, exist_ok=True) - home.mkdir(parents=True, exist_ok=True) - fixture.joinpath("src", "main.rs").write_text( - 'fn main() { println!("hello"); }\n', encoding="utf-8" - ) - run(["git", "init", "--quiet"], cwd=fixture, environment=os.environ.copy()) - run(["git", "add", "src/main.rs"], cwd=fixture, environment=os.environ.copy()) - run( - [ - "git", - "-c", - "user.name=TraceDecay MCP Smoke", - "-c", - "user.email=tracedecay-mcp-smoke@example.invalid", - "commit", - "--quiet", - "-m", - "test: seed MCP smoke fixture", - ], - cwd=fixture, - environment=os.environ.copy(), - ) - - environment = os.environ.copy() - environment.update( - { - "HOME": str(home), - "USERPROFILE": str(home), - "XDG_DATA_HOME": str(home / ".local" / "share"), - "XDG_CONFIG_HOME": str(home / ".config"), - "APPDATA": str(home / "AppData" / "Roaming"), - "LOCALAPPDATA": str(home / "AppData" / "Local"), - } - ) - socket_path = home / ".tracedecay" / "daemon.sock" - socket_path.parent.mkdir(parents=True, exist_ok=True) - environment = daemon_environment(environment, socket_path) - daemon = subprocess.Popen( - daemon_command(binary, socket_path), - cwd=fixture, - env=environment, - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - preexec_fn=dies_with_this_process(), - ) - try: - wait_for_daemon(daemon, binary, fixture, environment) - run([str(binary), "init"], cwd=fixture, environment=environment) - - first = inspect( - npx, - binary, - fixture, - environment, - "--method", - "tools/list", - ) - second = inspect( - npx, - binary, - fixture, - environment, - "--method", - "tools/list", - ) - if first.stdout != second.stdout: - raise SystemExit( - "MCP tools/list output changed across identical invocations" - ) - payload = json.loads(first.stdout) - tools = payload.get("tools") - if not isinstance(tools, list) or not tools: - raise SystemExit("MCP tools/list returned no tools") - names = { - tool.get("name") - for tool in tools - if isinstance(tool, dict) - and isinstance(tool.get("inputSchema"), dict) - and tool["inputSchema"].get("type") == "object" - } - missing = sorted(REQUIRED_TOOLS - names) - if missing: - raise SystemExit( - "MCP tools/list omitted required tools: " + ", ".join(missing) - ) - - diagnostics = inspect( - npx, - binary, - fixture, - environment, - "--method", - "tools/call", - "--tool-name", - "tracedecay_diagnostics", - check=False, - ) - try: - diagnostic_payload = json.loads(diagnostics.stdout) - except json.JSONDecodeError as error: - raise SystemExit( - "advertised tracedecay_diagnostics dispatch returned no typed payload: " - + diagnostics.stderr.strip() - ) from error - content = diagnostic_payload.get("content") - if not isinstance(content, list): - raise SystemExit("tracedecay_diagnostics omitted typed MCP content") - diagnostic_text = "\n".join( - item.get("text", "") - for item in content - if isinstance(item, dict) - and item.get("type") == "text" - and isinstance(item.get("text"), str) - ) - if not diagnostic_text: - raise SystemExit("tracedecay_diagnostics returned no typed text result") - lowered = diagnostic_text.lower() - if diagnostic_payload.get("isError") is True: - # A fixture without a configured compiler is a typed `unsupported` - # problem; a missing daemon authority is `unavailable`. - if problem_of(diagnostic_payload).get("kind") not in ( - "unavailable", - "unsupported", - ): - raise SystemExit( - "tracedecay_diagnostics error was not a typed unavailable or " - "unsupported problem: " + diagnostic_text - ) - else: - if diagnostics.returncode != 0: - raise SystemExit( - "tracedecay_diagnostics successful payload exited nonzero" - ) - if not any( - marker in lowered - for marker in ("diagnostic", "generation", "status", "unavailable") - ): - raise SystemExit( - "tracedecay_diagnostics success omitted concrete diagnostic state" - ) - - resources = inspect( - npx, - binary, - fixture, - environment, - "--method", - "resources/list", - ) - resource_payload = json.loads(resources.stdout) - if not any( - resource.get("uri") == "tracedecay://status" - for resource in resource_payload.get("resources", []) - if isinstance(resource, dict) - ): - raise SystemExit("MCP resources/list omitted tracedecay://status") - - unknown = inspect( - npx, - binary, - fixture, - environment, - "--method", - "tools/call", - "--tool-name", - "definitely_not_a_tool", - check=False, - ) - try: - unknown_payload = json.loads(unknown.stdout) - except json.JSONDecodeError as error: - raise SystemExit( - "unknown MCP tool returned no typed refusal: " + unknown.stderr.strip() - ) from error - if ( - unknown_payload.get("isError") is not True - or problem_of(unknown_payload).get("code") != "unknown_tool" - ): - raise SystemExit( - "unknown MCP tool was not a typed unknown_tool refusal: " - + unknown.stdout.strip() - ) - finally: - terminate(daemon) - - print("packaged MCP stdio acceptance passed") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/resolve-installed-binary.py b/scripts/resolve-installed-binary.py deleted file mode 100755 index 1bc8917dc6..0000000000 --- a/scripts/resolve-installed-binary.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -"""Resolve the cargo-installed TraceDecay binary across release platforms.""" - -from __future__ import annotations - -import argparse -import os -from pathlib import Path - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("install_root", type=Path) - parser.add_argument("runner_os") - arguments = parser.parse_args() - - name = "tracedecay.exe" if arguments.runner_os == "Windows" else "tracedecay" - binary = arguments.install_root.resolve() / "bin" / name - if not binary.is_file(): - raise SystemExit(f"installed binary layout did not produce {binary}") - if arguments.runner_os != "Windows" and not os.access(binary, os.X_OK): - raise SystemExit(f"cargo-installed binary is not executable: {binary}") - print(binary) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/resolve-release-source-profile.py b/scripts/resolve-release-source-profile.py deleted file mode 100755 index bd03055e38..0000000000 --- a/scripts/resolve-release-source-profile.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python3 -"""Resolve safe Cargo feature arguments for current or historical source tags.""" - -from __future__ import annotations - -import argparse -from pathlib import Path -import subprocess -import sys -import tomllib - - -def expand_local_features( - features: dict[str, list[str]], selected: list[str] -) -> set[str]: - resolved: set[str] = set() - pending = list(selected) - while pending: - feature = pending.pop() - if feature in resolved: - continue - resolved.add(feature) - for member in features.get(feature, []): - if member in features: - pending.append(member) - return resolved - - -def write_output(path: Path | None, name: str, value: str) -> None: - line = f"{name}={value}\n" - if path is None: - sys.stdout.write(line) - return - with path.open("a", encoding="utf-8") as handle: - handle.write(line) - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("--source", type=Path, required=True) - parser.add_argument("--target") - parser.add_argument("--github-output", type=Path) - arguments = parser.parse_args() - - source = arguments.source.resolve() - # The product package moved to `crates/tracedecay`; the repository root is - # now a virtual workspace manifest that declares no features at all. - # Reading the root here resolved every relocated tree to `legacy-default`, - # which silently drops `production` from the release feature set and skips - # the production feature-profile check below. - package_manifest = source.joinpath("crates", "tracedecay", "Cargo.toml") - if not package_manifest.is_file(): - raise SystemExit(f"missing product package manifest at {package_manifest}") - with package_manifest.open("rb") as handle: - manifest = tomllib.load(handle) - features = manifest.get("features", {}) - if not isinstance(features, dict): - raise SystemExit("source Cargo.toml has an invalid feature table") - defaults = features.get("default", []) - if not isinstance(defaults, list): - raise SystemExit("source Cargo.toml has an invalid feature table") - - if "production" in features: - checker = Path(__file__).with_name("check-production-feature-profile.py") - subprocess.run( - [sys.executable, str(checker), "--repo", str(source)], - check=True, - ) - profile = "production" - # Release archives ship the production profile only: no optional - # instrumentation feature lanes ride along to be switched off at - # runtime. - cargo_features = "production" - cargo_args = "--no-default-features --features production" - else: - resolved_defaults = expand_local_features(features, defaults) - if "test-transport" in resolved_defaults: - raise SystemExit( - "historical default features enable test-transport; refusing release" - ) - profile = "legacy-default" - cargo_args = "" - cargo_features = "" - - write_output(arguments.github_output, "profile", profile) - write_output(arguments.github_output, "cargo_args", cargo_args) - write_output(arguments.github_output, "cargo_features", cargo_features) - print(f"release source profile: {profile}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/test-check-distribution-clean-source.sh b/scripts/test-check-distribution-clean-source.sh deleted file mode 100644 index 9a937e3495..0000000000 --- a/scripts/test-check-distribution-clean-source.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P) -gate="$root/scripts/check-distribution-acceptance.sh" -work=$(mktemp -d "${TMPDIR:-/tmp}/tracedecay-clean-source-test.XXXXXX") -trap 'rm -rf -- "$work"' EXIT - -repo="$work/repo" -bin="$work/bin" -mkdir -p -- "$repo/.release-automation" "$bin" -for command in cargo rustc; do - cat >"$bin/$command" <<'SH' -#!/usr/bin/env bash -exit 2 -SH - chmod +x "$bin/$command" -done -cat >"$repo/Cargo.toml" <<'TOML' -[workspace] -members = [] -TOML - -git -C "$repo" init -q -git -C "$repo" config user.name "TraceDecay test" -git -C "$repo" config user.email "test@tracedecay.local" -git -C "$repo" add Cargo.toml -git -C "$repo" commit -qm "test fixture" -git -C "$repo/.release-automation" init -q - -output="$work/output" -if PATH="$bin:$PATH" "$gate" --repo "$repo" >"$output" 2>&1; then - echo "distribution gate unexpectedly accepted the incomplete fixture repository" >&2 - exit 1 -fi - -if grep -Fq "source repository has tracked or untracked drift" "$output"; then - echo "workflow-owned .release-automation checkout was treated as source drift" >&2 - exit 1 -fi -grep -Fq \ - "packaged host-event fixture copy differs from its authority: claude.json" \ - "$output" - -printf 'untracked source\n' >"$repo/untracked.txt" -if PATH="$bin:$PATH" "$gate" --repo "$repo" >"$output" 2>&1; then - echo "distribution gate unexpectedly accepted real untracked source drift" >&2 - exit 1 -fi -grep -Fq "source repository has tracked or untracked drift (?? untracked.txt)" "$output" - -printf 'distribution clean-source regression passed\n' diff --git a/scripts/test-check-distribution-feature-wiring.py b/scripts/test-check-distribution-feature-wiring.py deleted file mode 100644 index 7401b79959..0000000000 --- a/scripts/test-check-distribution-feature-wiring.py +++ /dev/null @@ -1,305 +0,0 @@ -#!/usr/bin/env python3 -"""Regression tests for distribution feature ownership validation.""" - -from __future__ import annotations - -from dataclasses import dataclass -from pathlib import Path -import subprocess -import sys -import tempfile - - -VALIDATOR = Path(__file__).with_name("check-distribution-feature-wiring.py") - -ROOT_MANIFEST = """[package] -name = "tracedecay" -version = "0.1.0-beta.34" - -[dependencies] -tracedecay-code-index = { version = "0.1.0" } -tracedecay-application = { version = "0.1.0" } - -[features] -lite = ["tracedecay-code-index/lite", "tracedecay-code-index-runtime/lite"] -medium = ["tracedecay-code-index/medium"] -full = ["tracedecay-code-index/full", "tracedecay-code-index-runtime/full"] -lang-dart = ["tracedecay-code-index/lang-dart"] -lang-markdown = ["tracedecay-code-index/lang-markdown"] -token-counting = [] -test-transport = [] -""" - -CODE_INDEX_MANIFEST = """[package] -name = "tracedecay-code-index" -version = "0.1.0" - -[dependencies] -tracedecay-code-extraction = { version = "0.1.0" } - -[features] -lite = ["tracedecay-code-extraction/lite", "lang-markdown"] -medium = ["tracedecay-code-extraction/medium"] -full = ["tracedecay-code-extraction/full", "lang-markdown"] -lang-dart = ["tracedecay-code-extraction/lang-dart"] -lang-markdown = ["tracedecay-code-extraction/lang-markdown"] -""" - -EXTRACTION_MANIFEST = """[package] -name = "tracedecay-code-extraction" -version = "0.1.0" - -[features] -lite = ["lang-markdown"] -medium = ["lang-dart"] -full = ["medium", "lang-markdown"] -lang-dart = [] -lang-markdown = [] -""" - -EXTRACTION_BUILD_MANIFEST = """[package] -name = "tracedecay-code-extraction" -version = "0.1.0" -edition = "2024" - -[features] -default = [] -lang-dart = ["dep:dart-grammar"] -lang-markdown = ["dep:markdown-grammar"] - -[dependencies] -dart-grammar = { path = "dart-grammar", optional = true } -markdown-grammar = { path = "markdown-grammar", optional = true } -""" - -EXTRACTION_BUILD_LIB = """#[cfg(feature = "lang-dart")] -pub fn dart() -> dart_grammar::Language { - dart_grammar::language() -} - -#[cfg(feature = "lang-markdown")] -pub fn markdown() -> markdown_grammar::Language { - markdown_grammar::language() -} -""" - -GRAMMAR_MANIFEST = """[package] -name = "{name}" -version = "0.1.0" -edition = "2024" - -[lib] -path = "lib.rs" -""" - -GRAMMAR_LIB = """pub struct Language; - -pub fn language() -> Language { - Language -} -""" - -CLI_MANIFEST = """[package] -name = "tracedecay-cli" -version = "0.1.0" - -[dependencies] -tracedecay = { version = "0.1.0" } - -[features] -default = ["production"] -production = ["tracedecay/production"] -""" - - -@dataclass(frozen=True) -class FixtureResult: - returncode: int - stdout: str - stderr: str - - -def run_fixture( - root_source: str = ROOT_MANIFEST, - root_packaged: str = ROOT_MANIFEST, - code_index_source: str = CODE_INDEX_MANIFEST, - code_index_packaged: str = CODE_INDEX_MANIFEST, - extraction_source: str = EXTRACTION_MANIFEST, - extraction_packaged: str = EXTRACTION_MANIFEST, - cli_source: str = CLI_MANIFEST, - cli_packaged: str = CLI_MANIFEST, - extraction_build_manifest: str | None = None, -) -> FixtureResult: - with tempfile.TemporaryDirectory() as temporary_directory: - root = Path(temporary_directory) - manifests = { - "root-source.toml": root_source, - "root-packaged.toml": root_packaged, - "code-index-source.toml": code_index_source, - "code-index-packaged.toml": code_index_packaged, - "extraction-source.toml": extraction_source, - "extraction-packaged.toml": extraction_packaged, - "cli-source.toml": cli_source, - "cli-packaged.toml": cli_packaged, - } - for name, contents in manifests.items(): - root.joinpath(name).write_text(contents, encoding="utf-8") - command = [ - sys.executable, - str(VALIDATOR), - "--root-source", - str(root / "root-source.toml"), - "--root-packaged", - str(root / "root-packaged.toml"), - "--code-index-source", - str(root / "code-index-source.toml"), - "--code-index-packaged", - str(root / "code-index-packaged.toml"), - "--extraction-source", - str(root / "extraction-source.toml"), - "--extraction-packaged", - str(root / "extraction-packaged.toml"), - "--cli-source", - str(root / "cli-source.toml"), - "--cli-packaged", - str(root / "cli-packaged.toml"), - ] - if extraction_build_manifest is not None: - extraction = root / "extraction-build" - extraction.joinpath("src").mkdir(parents=True) - extraction.joinpath("Cargo.toml").write_text( - extraction_build_manifest, encoding="utf-8" - ) - extraction.joinpath("src/lib.rs").write_text( - EXTRACTION_BUILD_LIB, encoding="utf-8" - ) - for grammar in ("dart-grammar", "markdown-grammar"): - dependency = extraction / grammar - dependency.mkdir() - dependency.joinpath("Cargo.toml").write_text( - GRAMMAR_MANIFEST.format(name=grammar), encoding="utf-8" - ) - dependency.joinpath("lib.rs").write_text( - GRAMMAR_LIB, encoding="utf-8" - ) - command.extend( - ["--check-extraction-manifest", str(extraction / "Cargo.toml")] - ) - completed = subprocess.run( - command, - check=False, - capture_output=True, - text=True, - ) - return FixtureResult(completed.returncode, completed.stdout, completed.stderr) - - -def main() -> int: - extracted_owner = run_fixture() - if extracted_owner.returncode != 0: - raise SystemExit(extracted_owner.stderr) - - cli_without_production = CLI_MANIFEST.replace( - 'production = ["tracedecay/production"]\n', - "", - ) - missing_cli_production = run_fixture( - cli_source=cli_without_production, - cli_packaged=cli_without_production, - ) - if missing_cli_production.returncode == 0: - raise SystemExit("CLI without the production release feature was accepted") - if ( - "tracedecay-cli is missing required features" - not in missing_cli_production.stderr - ): - raise SystemExit("missing CLI production feature failed for an unexpected reason") - - cli_with_miswired_production = CLI_MANIFEST.replace( - 'production = ["tracedecay/production"]', - 'production = []', - ) - miswired_cli_production = run_fixture( - cli_source=cli_with_miswired_production, - cli_packaged=cli_with_miswired_production, - ) - if miswired_cli_production.returncode == 0: - raise SystemExit("CLI with a mountless production feature was accepted") - if ( - "tracedecay-cli production must enable" - not in miswired_cli_production.stderr - ): - raise SystemExit("miswired CLI production feature failed for an unexpected reason") - - # Internal forwarding topology is not a contract: rerouting a tier through - # a different intermediate edge, forwarding a language alias through the - # runtime crate, and adding a local tier member are all accepted without - # touching this validator. - rewired_root = ROOT_MANIFEST.replace( - 'lite = ["tracedecay-code-index/lite", "tracedecay-code-index-runtime/lite"]', - 'lite = ["tracedecay-code-index-runtime/lite"]', - ).replace( - 'lang-dart = ["tracedecay-code-index/lang-dart"]', - 'lang-dart = ["tracedecay-code-index-runtime/lang-dart"]', - ).replace( - 'full = ["tracedecay-code-index/full", "tracedecay-code-index-runtime/full"]', - 'full = ["tracedecay-code-index/full", "tracedecay-code-index-runtime/full", "lang-markdown"]', - ) - rewired_code_index = CODE_INDEX_MANIFEST.replace( - 'lang-dart = ["tracedecay-code-extraction/lang-dart"]\n', "" - ) - rewired = run_fixture( - root_source=rewired_root, - root_packaged=rewired_root, - code_index_source=rewired_code_index, - code_index_packaged=rewired_code_index, - ) - if rewired.returncode != 0: - raise SystemExit( - "behavior-preserving forwarding change was rejected: " + rewired.stderr - ) - - packaged_root_drift = run_fixture( - root_packaged=ROOT_MANIFEST.replace('lang-dart = ["tracedecay-code-index/lang-dart"]\n', "") - ) - if packaged_root_drift.returncode == 0: - raise SystemExit("packaged root manifest dropping a feature was accepted") - if "packaged root feature wiring differs" not in packaged_root_drift.stderr: - raise SystemExit("packaged root drift failed for an unexpected reason") - - extraction_with_new_language = EXTRACTION_MANIFEST.replace( - "lang-markdown = []\n", "lang-markdown = []\nlang-rustdoc = []\n" - ) - unforwarded_language = run_fixture( - extraction_source=extraction_with_new_language, - extraction_packaged=extraction_with_new_language, - ) - if unforwarded_language.returncode == 0: - raise SystemExit("new extraction language without public aliases was accepted") - if "root language features differ" not in unforwarded_language.stderr: - raise SystemExit("unforwarded language failed for an unexpected reason") - - isolated_languages = run_fixture( - extraction_build_manifest=EXTRACTION_BUILD_MANIFEST - ) - if isolated_languages.returncode != 0: - raise SystemExit(isolated_languages.stderr) - - extraction_with_miswired_language = EXTRACTION_BUILD_MANIFEST.replace( - 'lang-dart = ["dep:dart-grammar"]', - 'lang-dart = ["dep:markdown-grammar"]', - ) - miswired_language = run_fixture( - extraction_build_manifest=extraction_with_miswired_language - ) - if miswired_language.returncode == 0: - raise SystemExit("miswired isolated extraction language was accepted") - if "lang-dart does not compile in isolation" not in miswired_language.stderr: - raise SystemExit("miswired extraction language failed for an unexpected reason") - - print("distribution feature wiring fixtures passed") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/test-check-distribution-reuse.sh b/scripts/test-check-distribution-reuse.sh deleted file mode 100755 index 736a647c5a..0000000000 --- a/scripts/test-check-distribution-reuse.sh +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P) -gate="$root/scripts/check-distribution-acceptance.sh" -work=$(mktemp -d "${TMPDIR:-/tmp}/tracedecay-reuse-test.XXXXXX") -trap 'rm -rf -- "$work"' EXIT - -repo="$work/repo" -bin="$work/bin" -state="$work/state" -mkdir -p -- \ - "$repo/crates/tracedecay" \ - "$repo/crates/tracedecay-cli" \ - "$repo/crates/tracedecay-hooks/fixtures/host_events" \ - "$repo/tests/fixtures/packaged_host_events" \ - "$repo/.cargo" \ - "$repo/plugin" \ - "$repo/vendor" \ - "$repo/benchmark_data" \ - "$repo/dashboard/hermes-wrapper" \ - "$repo/dashboard/app-dist" \ - "$repo/scripts" \ - "$bin" \ - "$state" - -cat >"$repo/Cargo.toml" <<'TOML' -[workspace] -members = ["crates/tracedecay", "crates/tracedecay-cli"] - -[workspace.package] -version = "0.0.0" -TOML -cat >"$repo/crates/tracedecay/Cargo.toml" <<'TOML' -[package] -name = "tracedecay" -version = "0.0.0" -readme = "../../README.md" -TOML -cat >"$repo/crates/tracedecay-cli/Cargo.toml" <<'TOML' -[package] -name = "tracedecay-cli" -version = "0.0.0" -TOML - -for fixture in \ - claude.json \ - claude/post_tool_use_write.json \ - codex.json \ - cline-family.json \ - cursor.json \ - hermes.json \ - hermes/saved-edit.json \ - hermes/terminal-receipt.json \ - kiro.json \ - kimi-code.json \ - kimi/post-tool-use-edit.json \ - opencode/baseline.json \ - pi.json \ - droid.json; do - mkdir -p -- \ - "$repo/crates/tracedecay-hooks/fixtures/host_events/$(dirname -- "$fixture")" \ - "$repo/tests/fixtures/packaged_host_events/$(dirname -- "$fixture")" - printf 'fixture\n' >"$repo/crates/tracedecay-hooks/fixtures/host_events/$fixture" - printf 'fixture\n' >"$repo/tests/fixtures/packaged_host_events/$fixture" -done - -printf 'original readme\n' >"$repo/README.md" -printf 'changelog\n' >"$repo/CHANGELOG.md" -printf 'license\n' >"$repo/LICENSE" -printf 'stable\n' >"$repo/rust-toolchain.toml" -printf '[net]\noffline = true\n' >"$repo/.cargo/config.toml" -printf 'plugin\n' >"$repo/plugin/fixture" -printf 'vendor\n' >"$repo/vendor/fixture" -printf 'benchmark\n' >"$repo/benchmark_data/fixture" -printf 'wrapper\n' >"$repo/dashboard/hermes-wrapper/fixture" -printf 'bundle\n' >"$repo/dashboard/app-dist/fixture" -printf '#!/usr/bin/env bash\n' >"$repo/scripts/run-session-temporal-benchmark.sh" - -git -C "$repo" init -q -git -C "$repo" config user.name "TraceDecay test" -git -C "$repo" config user.email "test@tracedecay.local" -git -C "$repo" add -A -git -C "$repo" commit -qm "test fixture" -source_sha=$(git -C "$repo" rev-parse HEAD) - -real_python=$(command -v python3) - -cat >"$bin/tracedecay" <"$bin/cargo" <<'SH' -#!/usr/bin/env bash -set -euo pipefail -printf '%s\n' "$*" >>"$TEST_STATE/cargo.log" -case "${1:-}" in - build) - if [[ " $* " == *" --workspace "* ]]; then - printf 'workspace-rebuild\n' >>"$TEST_STATE/cargo.log" - exit 3 - fi - exit 0 - ;; - package) - exit 77 - ;; - *) - exit 2 - ;; -esac -SH -cat >"$bin/rustc" <<'SH' -#!/usr/bin/env bash -if [[ ${1:-} == -vV ]]; then - printf 'rustc 1.0.0\nbinary: rustc\nhost: x86_64-unknown-linux-gnu\n' - exit 0 -fi -exit 2 -SH -cat >"$bin/python3" <<'SH' -#!/usr/bin/env bash -set -euo pipefail -case "${1:-}" in - */resolve-release-source-profile.py) - output= - while (($#)); do - if [[ $1 == --github-output ]]; then - output=$2 - break - fi - shift - done - printf 'profile=production\ncargo_features=production\n' >"$output" - ;; - *) - exec "$REAL_PYTHON" "$@" - ;; -esac -SH -chmod +x "$bin/cargo" "$bin/rustc" "$bin/python3" - -run_gate() { - local output=$1 - shift - set +e - PATH="$bin:$PATH" \ - REAL_PYTHON="$real_python" \ - TEST_STATE="$state" \ - TMPDIR="$work" \ - "$gate" --repo "$repo" "$@" >"$output" 2>&1 - local status=$? - set -e - printf '%s\n' "$status" -} - -output="$work/output" - -: >"$state/cargo.log" -status=$(run_gate "$output" --skip-packaged-runtime-battery) -[[ $status -ne 0 ]] || { - cat "$output" >&2 - echo "skip-packaged-runtime-battery without a reused binary was accepted" >&2 - exit 1 -} -grep -Fq -- "--skip-packaged-runtime-battery requires --reuse-release-binary" "$output" || { - cat "$output" >&2 - echo "missing reuse-binary requirement was not reported" >&2 - exit 1 -} -if [[ -s $state/cargo.log ]]; then - echo "cargo ran before the reuse-binary requirement failed" >&2 - exit 1 -fi - -# Without a handed-in binary the gate goes straight to packaging: the -# packaged CLI it builds later is the production binary under test, so a -# source-tree workspace release build here would be a second compile of the -# same graph that nothing reads. -: >"$state/cargo.log" -status=$(run_gate "$output") -[[ $status -eq 77 ]] || { - cat "$output" >&2 - echo "default path did not stop at the controlled cargo package boundary" >&2 - exit 1 -} -if grep -Fxq "workspace-rebuild" "$state/cargo.log"; then - echo "default path invoked a source-tree workspace release build" >&2 - exit 1 -fi -if grep -Eq '^build( |$)' "$state/cargo.log"; then - echo "default path invoked cargo build before packaging" >&2 - exit 1 -fi -if grep -Fq "reusing the just-built production binary" "$output"; then - cat "$output" >&2 - echo "default path claimed to reuse a binary it was never given" >&2 - exit 1 -fi - -: >"$state/cargo.log" -status=$(run_gate "$output" --reuse-release-binary "$bin/tracedecay") -[[ $status -eq 77 ]] || { - cat "$output" >&2 - echo "reuse path did not stop at the controlled cargo package boundary" >&2 - exit 1 -} -grep -Fq "distribution acceptance: reusing the just-built production binary" "$output" || { - cat "$output" >&2 - echo "reuse path did not announce the just-built binary" >&2 - exit 1 -} -if grep -Fxq "workspace-rebuild" "$state/cargo.log"; then - echo "reuse path still invoked the workspace release rebuild" >&2 - exit 1 -fi -grep -Eq '^package( |$)' "$state/cargo.log" || { - echo "reuse path skipped cargo package" >&2 - exit 1 -} - -printf 'wrong version\n' >"$bin/bad-tracedecay" -chmod +x "$bin/bad-tracedecay" -cat >"$bin/bad-tracedecay" <<'SH' -#!/usr/bin/env bash -if [[ ${1:-} == --version ]]; then - printf 'tracedecay 9.9.9+deadbeef\n' - exit 0 -fi -exit 2 -SH -chmod +x "$bin/bad-tracedecay" -: >"$state/cargo.log" -status=$(run_gate "$output" --reuse-release-binary "$bin/bad-tracedecay") -[[ $status -ne 0 ]] || { - cat "$output" >&2 - echo "reuse path accepted a binary with the wrong source sha" >&2 - exit 1 -} -grep -Fq "reused-release tracedecay binary reported" "$output" || { - cat "$output" >&2 - echo "wrong reused binary failed for an unexpected reason" >&2 - exit 1 -} -if [[ -s $state/cargo.log ]]; then - echo "cargo ran after a reused binary failed its source-sha check" >&2 - exit 1 -fi - -printf 'distribution reuse-binary regression passed\n' diff --git a/scripts/test-check-distribution-snapshot.sh b/scripts/test-check-distribution-snapshot.sh deleted file mode 100644 index 5e4d15e870..0000000000 --- a/scripts/test-check-distribution-snapshot.sh +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P) -gate="$root/scripts/check-distribution-acceptance.sh" -work=$(mktemp -d "${TMPDIR:-/tmp}/tracedecay-snapshot-test.XXXXXX") -trap 'rm -rf -- "$work"' EXIT - -repo="$work/repo" -bin="$work/bin" -state="$work/state" -mkdir -p -- \ - "$repo/crates/tracedecay" \ - "$repo/crates/tracedecay-cli" \ - "$repo/crates/tracedecay-hooks/fixtures/host_events" \ - "$repo/tests/fixtures/packaged_host_events" \ - "$repo/.cargo" \ - "$repo/plugin" \ - "$repo/vendor" \ - "$repo/benchmark_data" \ - "$repo/dashboard/hermes-wrapper" \ - "$repo/dashboard/app-dist" \ - "$repo/scripts" \ - "$bin" \ - "$state" - -cat >"$repo/Cargo.toml" <<'TOML' -[workspace] -members = ["crates/tracedecay", "crates/tracedecay-cli"] - -[workspace.package] -version = "0.0.0" -TOML -cat >"$repo/crates/tracedecay/Cargo.toml" <<'TOML' -[package] -name = "tracedecay" -version = "0.0.0" -readme = "../../README.md" -TOML -cat >"$repo/crates/tracedecay-cli/Cargo.toml" <<'TOML' -[package] -name = "tracedecay-cli" -version = "0.0.0" -TOML - -for fixture in \ - claude.json \ - claude/post_tool_use_write.json \ - codex.json \ - cline-family.json \ - cursor.json \ - hermes.json \ - hermes/saved-edit.json \ - hermes/terminal-receipt.json \ - kiro.json \ - kimi-code.json \ - kimi/post-tool-use-edit.json \ - opencode/baseline.json \ - pi.json \ - droid.json; do - mkdir -p -- \ - "$repo/crates/tracedecay-hooks/fixtures/host_events/$(dirname -- "$fixture")" \ - "$repo/tests/fixtures/packaged_host_events/$(dirname -- "$fixture")" - printf 'fixture\n' >"$repo/crates/tracedecay-hooks/fixtures/host_events/$fixture" - printf 'fixture\n' >"$repo/tests/fixtures/packaged_host_events/$fixture" -done - -printf 'original readme\n' >"$repo/README.md" -printf 'changelog\n' >"$repo/CHANGELOG.md" -printf 'license\n' >"$repo/LICENSE" -printf 'stable\n' >"$repo/rust-toolchain.toml" -printf '[net]\noffline = true\n' >"$repo/.cargo/config.toml" -printf 'plugin\n' >"$repo/plugin/fixture" -printf 'vendor\n' >"$repo/vendor/fixture" -printf 'benchmark\n' >"$repo/benchmark_data/fixture" -printf 'wrapper\n' >"$repo/dashboard/hermes-wrapper/fixture" -printf 'bundle\n' >"$repo/dashboard/app-dist/fixture" -printf '#!/usr/bin/env bash\n' >"$repo/scripts/run-session-temporal-benchmark.sh" - -# The product crate carries its own `tests/fixtures`, which occupies the path -# the root asset of the same name is staged onto. -mkdir -p -- "$repo/crates/tracedecay/tests/fixtures/crate_local" -printf 'crate local\n' >"$repo/crates/tracedecay/tests/fixtures/crate_local/fixture" - -git -C "$repo" init -q -git -C "$repo" config user.name "TraceDecay test" -git -C "$repo" config user.email "test@tracedecay.local" -git -C "$repo" add -A -git -C "$repo" commit -qm "test fixture" - -real_cp=$(command -v cp) -real_python=$(command -v python3) - -# The package boundary is the last point where the gate's staged tree exists, -# so the staged-snapshot assertions run there and record that they did. -cat >"$bin/cargo" <<'SH' -#!/usr/bin/env bash -set -euo pipefail -case "${1:-}" in - build) exit 0 ;; - package) ;; - *) exit 2 ;; -esac -manifest= -while (($#)); do - if [[ $1 == --manifest-path ]]; then - manifest=$2 - break - fi - shift -done -staged=$(dirname -- "$manifest") -grep -Fxq "original readme" "$staged/crates/tracedecay/README.md" || { - echo "packaged asset was copied from the mutated live repository" >&2 - exit 1 -} -grep -Fxq "mutated live readme" "$TEST_REPO/README.md" -product_fixtures="$staged/crates/tracedecay/tests/fixtures" -[[ ! -e "$product_fixtures/crate_local" ]] || { - echo "crate-local content survived beside the staged root asset" >&2 - exit 1 -} -[[ -f "$product_fixtures/packaged_host_events/claude.json" ]] || { - echo "staged root asset is missing from the product package" >&2 - exit 1 -} -printf '%s\n' "$staged" >"$TEST_STATE/staged-checked" -exit 77 -SH -cat >"$bin/rustc" <<'SH' -#!/usr/bin/env bash -if [[ ${1:-} == -vV ]]; then - printf 'rustc 1.0.0\nbinary: rustc\nhost: x86_64-unknown-linux-gnu\n' - exit 0 -fi -exit 2 -SH -cat >"$bin/python3" <<'SH' -#!/usr/bin/env bash -set -euo pipefail -case "${1:-}" in - */resolve-release-source-profile.py) - output= - while (($#)); do - if [[ $1 == --github-output ]]; then - output=$2 - break - fi - shift - done - printf 'profile=production\ncargo_features=production\n' >"$output" - ;; - *) - exec "$REAL_PYTHON" "$@" - ;; -esac -SH -cat >"$bin/cp" <<'SH' -#!/usr/bin/env bash -set -euo pipefail -if [[ ! -e "$TEST_STATE/mutated" ]]; then - : >"$TEST_STATE/mutated" - printf 'mutated live readme\n' >"$TEST_REPO/README.md" -fi -exec "$REAL_CP" "$@" -SH -chmod +x "$bin/cargo" "$bin/rustc" "$bin/python3" "$bin/cp" - -output="$work/output" -set +e -PATH="$bin:$PATH" \ -REAL_CP="$real_cp" \ -REAL_PYTHON="$real_python" \ -TEST_REPO="$repo" \ -TEST_STATE="$state" \ -TMPDIR="$work" \ - "$gate" --repo "$repo" >"$output" 2>&1 -status=$? -set -e -[[ $status -eq 77 ]] || { - cat "$output" >&2 - echo "distribution gate stopped before the controlled cargo package boundary" >&2 - exit 1 -} - -staged=$(<"$state/staged-checked") -[[ $staged == "$work"/tracedecay-distribution.*/staged ]] || { - echo "staged-snapshot assertions did not run inside the gate's work directory: $staged" >&2 - exit 1 -} -leftover=$(find "$work" -maxdepth 1 -name 'tracedecay-distribution.*' -print) -[[ -z $leftover ]] || { - echo "distribution gate left its temporary directory behind: $leftover" >&2 - exit 1 -} - -printf 'distribution staged-snapshot regression passed\n' diff --git a/scripts/test-check-packaged-lsp-bridge.py b/scripts/test-check-packaged-lsp-bridge.py old mode 100644 new mode 100755 diff --git a/scripts/test-check-packaged-mcp-stdio.py b/scripts/test-check-packaged-mcp-stdio.py deleted file mode 100755 index 2c9423b43b..0000000000 --- a/scripts/test-check-packaged-mcp-stdio.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python3 -"""Fixture-driven Windows MCP stdio acceptance regression.""" - -from __future__ import annotations - -import importlib.util -import json -import os -from pathlib import Path -import subprocess -import sys -import tempfile - - -SMOKE = Path(__file__).with_name("check-packaged-mcp-stdio.py") -SPEC = importlib.util.spec_from_file_location("check_packaged_mcp_stdio", SMOKE) -if SPEC is None or SPEC.loader is None: - raise RuntimeError(f"could not load {SMOKE}") -MODULE = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(MODULE) -REQUIRED_TOOLS = [ - "tracedecay_search", - "tracedecay_diagnostics", - "tracedecay_impact", - "tracedecay_affected", - "tracedecay_test_map", -] - - -def write_executable(path: Path, source: str) -> None: - path.write_text(source, encoding="utf-8") - path.chmod(path.stat().st_mode | 0o111) - - -def write_python_launcher(path: Path, source: str) -> Path: - if os.name != "nt": - write_executable(path, f"#!/usr/bin/env python3\n{source}") - return path - - python_source = path.with_suffix(".py") - python_source.write_text(source, encoding="utf-8") - launcher = path.with_suffix(".cmd") - command = subprocess.list2cmdline([sys.executable, str(python_source)]) - launcher.write_text(f"@echo off\r\n{command} %*\r\n", encoding="utf-8") - return launcher - - -def main() -> int: - binary_path = Path("tracedecay.exe") - socket_path = Path("home/.tracedecay/daemon.sock") - assert MODULE.daemon_command(binary_path, socket_path, platform_name="nt") == [ - str(binary_path), - "daemon", - "run", - ] - assert "TRACEDECAY_DAEMON_SOCKET" not in MODULE.daemon_environment( - {"TRACEDECAY_DAEMON_SOCKET": "fake"}, socket_path, platform_name="nt" - ) - assert MODULE.daemon_command(binary_path, socket_path, platform_name="posix") == [ - str(binary_path), - "daemon", - "run", - "--socket", - str(socket_path), - ] - assert MODULE.daemon_environment( - {"HOME": "home"}, socket_path, platform_name="posix" - )["TRACEDECAY_DAEMON_SOCKET"] == str(socket_path) - - assert not MODULE.daemon_endpoint_is_ready(0, f"socket: {socket_path} (missing)") - assert not MODULE.daemon_endpoint_is_ready(0, f"socket: {socket_path} (stale)") - assert not MODULE.daemon_endpoint_is_ready( - 1, "endpoint: tcp://127.0.0.1:9 (connectable)" - ) - assert MODULE.daemon_endpoint_is_ready(0, f"socket: {socket_path} (connectable)") - assert MODULE.daemon_endpoint_is_ready( - 0, "endpoint: tcp://127.0.0.1:39181 (connectable)" - ) - - with tempfile.TemporaryDirectory() as temporary_directory: - root = Path(temporary_directory) - bin_directory = root / "bin" - bin_directory.mkdir() - binary = write_python_launcher( - bin_directory / "tracedecay", - """import os -import sys -import time -from pathlib import Path - -args = sys.argv[1:] -ready = Path(os.environ["HOME"]) / ".tracedecay" / "fixture-daemon-ready" -if args[:2] == ["daemon", "run"]: - ready.parent.mkdir(parents=True, exist_ok=True) - ready.write_text("", encoding="utf-8") - while True: - time.sleep(60) -if args == ["daemon", "status"]: - state = "connectable" if ready.exists() else "missing" - print(f"endpoint: fixture ({state})") - raise SystemExit(0) -if args == ["init"]: - raise SystemExit(0) -if args == ["fixture-probe-daemon"]: - raise SystemExit(0 if ready.exists() else 1) -raise SystemExit(1) -""", - ) - - tools = [ - {"name": name, "inputSchema": {"type": "object"}} for name in REQUIRED_TOOLS - ] - write_python_launcher( - bin_directory / "npx", - f"""import json -import subprocess -import sys - -arguments = sys.argv[1:] -binary = arguments[arguments.index("--cli") + 1] -subprocess.run([binary, "fixture-probe-daemon"], check=True) -method = arguments[arguments.index("--method") + 1] -if method == "tools/list": - print(json.dumps({{"tools": {json.dumps(tools)}}})) -elif method == "resources/list": - print(json.dumps({{"resources": [{{"uri": "tracedecay://status"}}]}})) -elif method == "tools/call": - tool = arguments[arguments.index("--tool-name") + 1] - if tool == "tracedecay_diagnostics": - print(json.dumps({{ - "isError": True, - "content": [{{ - "type": "text", - "text": "daemon diagnostic authority is unavailable", - }}], - "structuredContent": {{"problem": {{"kind": "unavailable"}}}}, - }})) - raise SystemExit(2) - elif tool == "definitely_not_a_tool": - print(json.dumps({{ - "isError": True, - "content": [{{"type": "text", "text": "unknown tool"}}], - "structuredContent": {{"problem": {{"code": "unknown_tool"}}}}, - }})) - else: - raise SystemExit(1) -else: - raise SystemExit(1) -""", - ) - - environment = os.environ.copy() - environment["PATH"] = str(bin_directory) + os.pathsep + environment["PATH"] - completed = subprocess.run( - [sys.executable, str(SMOKE), str(binary), str(root / "work")], - env=environment, - check=False, - capture_output=True, - text=True, - ) - if completed.returncode != 0: - raise SystemExit(completed.stderr or completed.stdout) - if "packaged MCP stdio acceptance passed" not in completed.stdout: - raise SystemExit("Windows MCP fixture did not complete the real-tool smoke") - - print("Windows packaged MCP stdio fixture passed") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/test-resolve-installed-binary.py b/scripts/test-resolve-installed-binary.py deleted file mode 100755 index 6801dc59bc..0000000000 --- a/scripts/test-resolve-installed-binary.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -"""Cross-platform fixtures for installed binary path resolution.""" - -from __future__ import annotations - -import os -from pathlib import Path -import subprocess -import sys -import tempfile - - -RESOLVER = Path(__file__).with_name("resolve-installed-binary.py") - - -def resolve(root: Path, runner_os: str) -> subprocess.CompletedProcess[str]: - return subprocess.run( - [sys.executable, str(RESOLVER), str(root), runner_os], - check=False, - capture_output=True, - text=True, - ) - - -def main() -> int: - with tempfile.TemporaryDirectory() as temporary_directory: - root = Path(temporary_directory) - binary_directory = root / "bin" - binary_directory.mkdir() - windows_binary = binary_directory / "tracedecay.exe" - windows_binary.write_bytes(b"MZ") - - windows = resolve(root, "Windows") - if windows.returncode != 0: - raise SystemExit(windows.stderr) - if Path(windows.stdout.strip()) != windows_binary.resolve(): - raise SystemExit("Windows resolver did not return tracedecay.exe") - - unix_binary = binary_directory / "tracedecay" - extensionless = resolve(root, "Linux") - if extensionless.returncode == 0: - raise SystemExit("Unix resolver incorrectly accepted tracedecay.exe") - if extensionless.stderr != ( - f"installed binary layout did not produce {unix_binary.resolve()}\n" - ): - raise SystemExit(f"Unix resolver failed for an unexpected reason: {extensionless.stderr}") - - windows_binary.unlink() - unix_binary.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8") - if os.name != "nt": - unix_binary.chmod(0o644) - unexecutable = resolve(root, "Linux") - if unexecutable.stderr != ( - f"cargo-installed binary is not executable: {unix_binary.resolve()}\n" - ): - raise SystemExit( - f"Unix resolver accepted a non-executable binary: {unexecutable.stderr}" - ) - unix_binary.chmod(unix_binary.stat().st_mode | 0o111) - unix = resolve(root, "Linux") - if unix.returncode != 0: - raise SystemExit(unix.stderr) - if Path(unix.stdout.strip()) != unix_binary.resolve(): - raise SystemExit("Unix resolver did not return tracedecay") - - print("installed binary path fixtures passed") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/test-resolve-release-source-profile.py b/scripts/test-resolve-release-source-profile.py deleted file mode 100755 index 2c5ab01790..0000000000 --- a/scripts/test-resolve-release-source-profile.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python3 -"""Regression tests for historical-tag release profile resolution.""" - -from __future__ import annotations - -from dataclasses import dataclass -from pathlib import Path -import subprocess -import sys -import tempfile - - -RESOLVER = Path(__file__).with_name("resolve-release-source-profile.py") - - -@dataclass(frozen=True) -class FixtureResult: - returncode: int - stdout: str - stderr: str - github_output: str - - -def run_fixture(manifest: str) -> FixtureResult: - with tempfile.TemporaryDirectory() as temporary_directory: - source = Path(temporary_directory) - # The resolver reads the product package manifest, not the workspace - # root: `crates/tracedecay/Cargo.toml` is where the feature table lives. - product = source.joinpath("crates", "tracedecay") - product.joinpath("src").mkdir(parents=True) - product.joinpath("Cargo.toml").write_text(manifest, encoding="utf-8") - product.joinpath("src", "lib.rs").write_text("", encoding="utf-8") - # The resolver walks the resolved dependency graph with `cargo tree - # --locked`, so the fixture is a real workspace with a lockfile. It - # has no dependencies, so the lockfile resolves offline. - source.joinpath("Cargo.toml").write_text( - '[workspace]\nmembers = ["crates/tracedecay"]\nresolver = "2"\n', - encoding="utf-8", - ) - subprocess.run( - ["cargo", "generate-lockfile", "--offline"], - cwd=source, - check=True, - capture_output=True, - ) - output = source / "github-output.txt" - completed = subprocess.run( - [ - sys.executable, - str(RESOLVER), - "--source", - str(source), - "--github-output", - str(output), - ], - check=False, - capture_output=True, - text=True, - ) - return FixtureResult( - returncode=completed.returncode, - stdout=completed.stdout, - stderr=completed.stderr, - github_output=output.read_text(encoding="utf-8") if output.exists() else "", - ) - - -def main() -> int: - production = run_fixture( - """[package] -name = "tracedecay" -version = "0.1.0" -edition = "2024" - -[features] -default = ["production"] -production = ["token-counting", "lite", "full"] -token-counting = [] -lite = [] -full = [] -""" - ) - if production.returncode != 0: - raise SystemExit(production.stderr) - if production.github_output != ( - "profile=production\n" - "cargo_args=--no-default-features --features production\n" - "cargo_features=production\n" - ): - raise SystemExit( - f"unexpected production profile output: {production.github_output!r}" - ) - - contaminated_production = run_fixture( - """[package] -name = "tracedecay" -version = "0.1.0" -edition = "2024" - -[features] -default = ["production"] -production = ["token-counting", "lite", "full", "test-transport"] -token-counting = [] -lite = [] -full = [] -test-transport = [] -""" - ) - if contaminated_production.returncode == 0: - raise SystemExit("production test-transport contamination was accepted") - if not contaminated_production.stderr.startswith( - "production feature directly enables test-transport\n" - ): - raise SystemExit( - "production contamination failed for an unexpected reason: " - f"{contaminated_production.stderr}" - ) - if contaminated_production.github_output: - raise SystemExit("refused production profile still wrote release outputs") - - legacy = run_fixture( - """[package] -name = "tracedecay" -version = "0.0.67" -edition = "2021" - -[features] -default = ["full", "token-counting"] -full = ["medium"] -medium = [] -token-counting = [] -test-transport = [] -""" - ) - if legacy.returncode != 0: - raise SystemExit(legacy.stderr) - if legacy.github_output != ( - "profile=legacy-default\ncargo_args=\ncargo_features=\n" - ): - raise SystemExit( - f"unexpected historical profile output: {legacy.github_output!r}" - ) - - contaminated = run_fixture( - """[package] -name = "tracedecay" -version = "0.0.1" -edition = "2021" - -[features] -default = ["full"] -full = ["test-transport"] -test-transport = [] -""" - ) - if contaminated.returncode == 0: - raise SystemExit("historical default test-transport contamination was accepted") - if "default features enable test-transport" not in contaminated.stderr: - raise SystemExit("historical contamination failed for an unexpected reason") - - print("historical release source profile fixtures passed") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/test-rust-cache-lineage.py b/scripts/test-rust-cache-lineage.py index cfe21e4a8c..a5ffe327e7 100755 --- a/scripts/test-rust-cache-lineage.py +++ b/scripts/test-rust-cache-lineage.py @@ -17,8 +17,8 @@ LINEAGE_PATH = ROOT / "scripts/rust_cache_lineage.py" CHECKER_PATH = ROOT / "scripts/check-rust-cache-lineage.py" WORKFLOWS = ROOT / ".github/workflows" -# The packaging battery still builds with Cargo behind rust-cache. -FIXTURE = "distribution-acceptance.yml" +# Hawk still uses the Cargo cache for its optional diagnostics. +FIXTURE = "hawk.yml" def load_module(path: Path, name: str) -> ModuleType: @@ -87,7 +87,7 @@ def _assert_rejected_after(self, old: str, new: str, reason: str) -> None: def test_rejects_rooting_rust_cache_at_the_vendored_checkout(self) -> None: self._assert_rejected_after( - "crates -> ../target\n", + "crates -> ../target/hawk\n", ". -> target\n", f"{FIXTURE} rust-cache step must list workspace root 'crates -> ../target'", )